github.com/openfga/openfga@v1.5.4-rc1/tests/functional_test.go (about)

     1  package tests
     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/grpc/codes"
    13  	"google.golang.org/grpc/metadata"
    14  	"google.golang.org/grpc/status"
    15  	"google.golang.org/protobuf/testing/protocmp"
    16  	"google.golang.org/protobuf/types/known/wrapperspb"
    17  
    18  	"github.com/openfga/openfga/internal/server/config"
    19  	checktest "github.com/openfga/openfga/internal/test/check"
    20  	"github.com/openfga/openfga/pkg/testutils"
    21  	"github.com/openfga/openfga/pkg/tuple"
    22  	"github.com/openfga/openfga/pkg/typesystem"
    23  )
    24  
    25  // newOpenFGAServerAndClient starts an OpenFGA server, waits until its is healthy, and returns a grpc client to it.
    26  func newOpenFGAServerAndClient(t *testing.T) openfgav1.OpenFGAServiceClient {
    27  	cfg := config.MustDefaultConfig()
    28  	cfg.Log.Level = "error"
    29  	cfg.Datastore.Engine = "memory"
    30  
    31  	StartServer(t, cfg)
    32  	conn := testutils.CreateGrpcConnection(t, cfg.GRPC.Addr)
    33  
    34  	testutils.EnsureServiceHealthy(t, cfg.GRPC.Addr, cfg.HTTP.Addr, nil, true)
    35  
    36  	client := openfgav1.NewOpenFGAServiceClient(conn)
    37  	return client
    38  }
    39  
    40  func TestGRPCMaxMessageSize(t *testing.T) {
    41  	client := newOpenFGAServerAndClient(t)
    42  
    43  	createResp, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{
    44  		Name: "max_message_size",
    45  	})
    46  	require.NoError(t, err)
    47  	require.NotPanics(t, func() { ulid.MustParse(createResp.GetId()) })
    48  
    49  	storeID := createResp.GetId()
    50  
    51  	model := parser.MustTransformDSLToProto(`model
    52    schema 1.1
    53  
    54  type user
    55  
    56  type document
    57    relations
    58      define viewer: [user with conds]
    59  
    60  condition conds(s: string) {
    61    "alpha" == s
    62  }`)
    63  
    64  	writeModelResp, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{
    65  		StoreId:         storeID,
    66  		TypeDefinitions: model.GetTypeDefinitions(),
    67  		Conditions:      model.GetConditions(),
    68  		SchemaVersion:   typesystem.SchemaVersion1_1,
    69  	})
    70  	require.NoError(t, err)
    71  	require.NotPanics(t, func() { ulid.MustParse(writeModelResp.GetAuthorizationModelId()) })
    72  
    73  	modelID := writeModelResp.GetAuthorizationModelId()
    74  
    75  	checkResp, err := client.Check(context.Background(), &openfgav1.CheckRequest{
    76  		StoreId:              storeID,
    77  		AuthorizationModelId: modelID,
    78  		TupleKey: &openfgav1.CheckRequestTupleKey{
    79  			Object:   "document:1",
    80  			Relation: "viewer",
    81  			User:     "user:jon",
    82  		},
    83  		Context: testutils.MustNewStruct(t, map[string]interface{}{
    84  			"s": testutils.CreateRandomString(config.DefaultMaxRPCMessageSizeInBytes + 1),
    85  		}),
    86  	})
    87  	s, ok := status.FromError(err)
    88  	require.True(t, ok)
    89  	require.Equal(t, codes.ResourceExhausted, s.Code())
    90  	require.ErrorContains(t, err, "grpc: received message larger than max")
    91  	require.Nil(t, checkResp)
    92  }
    93  
    94  // TODO make a unit test from this.
    95  func TestCheckWithQueryCacheEnabled(t *testing.T) {
    96  	cfg := config.MustDefaultConfig()
    97  	cfg.CheckQueryCache.Enabled = true
    98  
    99  	StartServer(t, cfg)
   100  
   101  	conn := testutils.CreateGrpcConnection(t, cfg.GRPC.Addr)
   102  
   103  	client := openfgav1.NewOpenFGAServiceClient(conn)
   104  
   105  	tests := []struct {
   106  		name            string
   107  		typeDefinitions []*openfgav1.TypeDefinition
   108  		tuples          []*openfgav1.TupleKey
   109  		assertions      []checktest.Assertion
   110  	}{
   111  		{
   112  			name: "issue_1058",
   113  			typeDefinitions: parser.MustTransformDSLToProto(`model
   114  	schema 1.1
   115  type fga_user
   116  
   117  type timeslot
   118    relations
   119  	define user: [fga_user]
   120  
   121  type commerce_store
   122    relations
   123  	define approved_hourly_access: user from approved_timeslot and hourly_employee
   124  	define approved_timeslot: [timeslot]
   125  	define hourly_employee: [fga_user]
   126  `).GetTypeDefinitions(),
   127  			tuples: []*openfgav1.TupleKey{
   128  				{Object: "commerce_store:0", Relation: "hourly_employee", User: "fga_user:anne"},
   129  				{Object: "commerce_store:1", Relation: "hourly_employee", User: "fga_user:anne"},
   130  				{Object: "commerce_store:0", Relation: "approved_timeslot", User: "timeslot:11_12"},
   131  				{Object: "commerce_store:1", Relation: "approved_timeslot", User: "timeslot:12_13"},
   132  			},
   133  			assertions: []checktest.Assertion{
   134  				{
   135  					Tuple: tuple.NewTupleKey("commerce_store:0", "approved_hourly_access", "fga_user:anne"),
   136  					ContextualTuples: []*openfgav1.TupleKey{
   137  						tuple.NewTupleKey("timeslot:11_12", "user", "fga_user:anne"),
   138  					},
   139  					Expectation: true,
   140  				},
   141  				{
   142  					Tuple: tuple.NewTupleKey("commerce_store:1", "approved_hourly_access", "fga_user:anne"),
   143  					ContextualTuples: []*openfgav1.TupleKey{
   144  						tuple.NewTupleKey("timeslot:11_12", "user", "fga_user:anne"),
   145  					},
   146  					Expectation: false,
   147  				},
   148  				{
   149  					Tuple: tuple.NewTupleKey("commerce_store:1", "approved_hourly_access", "fga_user:anne"),
   150  					ContextualTuples: []*openfgav1.TupleKey{
   151  						tuple.NewTupleKey("timeslot:12_13", "user", "fga_user:anne"),
   152  					},
   153  					Expectation: true,
   154  				},
   155  			},
   156  		},
   157  		{
   158  			name: "cache_computed_userset_subproblem_with_contextual_tuple",
   159  			typeDefinitions: parser.MustTransformDSLToProto(`model
   160  	schema 1.1
   161  type user
   162  
   163  type document
   164    relations
   165  	define restricted: [user]
   166  	define viewer: [user] but not restricted
   167  `).GetTypeDefinitions(),
   168  			tuples: []*openfgav1.TupleKey{
   169  				{Object: "document:1", Relation: "viewer", User: "user:jon"},
   170  			},
   171  			assertions: []checktest.Assertion{
   172  				{
   173  					Tuple:            tuple.NewTupleKey("document:1", "viewer", "user:jon"),
   174  					ContextualTuples: []*openfgav1.TupleKey{},
   175  					Expectation:      true,
   176  				},
   177  				{
   178  					Tuple: tuple.NewTupleKey("document:1", "viewer", "user:jon"),
   179  					ContextualTuples: []*openfgav1.TupleKey{
   180  						tuple.NewTupleKey("document:1", "restricted", "user:jon"),
   181  					},
   182  					Expectation: false,
   183  				},
   184  			},
   185  		},
   186  		{
   187  			name: "cached_direct_relationship_with_contextual_tuple",
   188  			typeDefinitions: parser.MustTransformDSLToProto(`model
   189  	schema 1.1
   190  type user
   191  
   192  type document
   193    relations
   194  	define viewer: [user]
   195  `).GetTypeDefinitions(),
   196  			assertions: []checktest.Assertion{
   197  				{
   198  					Tuple:            tuple.NewTupleKey("document:1", "viewer", "user:jon"),
   199  					ContextualTuples: []*openfgav1.TupleKey{},
   200  					Expectation:      false,
   201  				},
   202  				{
   203  					Tuple: tuple.NewTupleKey("document:1", "viewer", "user:jon"),
   204  					ContextualTuples: []*openfgav1.TupleKey{
   205  						tuple.NewTupleKey("document:1", "viewer", "user:jon"),
   206  					},
   207  					Expectation: true,
   208  				},
   209  			},
   210  		},
   211  		{
   212  			name: "cached_direct_userset_relationship_with_contextual_tuple",
   213  			typeDefinitions: parser.MustTransformDSLToProto(`model
   214  	schema 1.1
   215  type user
   216  
   217  type group
   218    relations
   219  	define restricted: [user]
   220  	define member: [user] but not restricted
   221  
   222  type document
   223    relations
   224  	define viewer: [group#member]
   225  `).GetTypeDefinitions(),
   226  			tuples: []*openfgav1.TupleKey{
   227  				{Object: "document:1", Relation: "viewer", User: "group:eng#member"},
   228  				{Object: "group:eng", Relation: "member", User: "user:jon"},
   229  			},
   230  			assertions: []checktest.Assertion{
   231  				{
   232  					Tuple:            tuple.NewTupleKey("document:1", "viewer", "user:jon"),
   233  					ContextualTuples: []*openfgav1.TupleKey{},
   234  					Expectation:      true,
   235  				},
   236  				{
   237  					Tuple: tuple.NewTupleKey("document:1", "viewer", "user:jon"),
   238  					ContextualTuples: []*openfgav1.TupleKey{
   239  						tuple.NewTupleKey("group:eng", "restricted", "user:jon"),
   240  					},
   241  					Expectation: false,
   242  				},
   243  			},
   244  		},
   245  	}
   246  
   247  	for _, test := range tests {
   248  		test := test
   249  
   250  		t.Run(test.name, func(t *testing.T) {
   251  			createResp, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{
   252  				Name: test.name,
   253  			})
   254  			require.NoError(t, err)
   255  			require.NotPanics(t, func() { ulid.MustParse(createResp.GetId()) })
   256  
   257  			storeID := createResp.GetId()
   258  
   259  			writeModelResp, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{
   260  				StoreId:         storeID,
   261  				TypeDefinitions: test.typeDefinitions,
   262  				SchemaVersion:   typesystem.SchemaVersion1_1,
   263  			})
   264  			require.NoError(t, err)
   265  			require.NotPanics(t, func() { ulid.MustParse(writeModelResp.GetAuthorizationModelId()) })
   266  
   267  			modelID := writeModelResp.GetAuthorizationModelId()
   268  
   269  			if len(test.tuples) > 0 {
   270  				_, err = client.Write(context.Background(), &openfgav1.WriteRequest{
   271  					StoreId:              storeID,
   272  					AuthorizationModelId: modelID,
   273  					Writes: &openfgav1.WriteRequestWrites{
   274  						TupleKeys: test.tuples,
   275  					},
   276  				})
   277  				require.NoError(t, err)
   278  			}
   279  
   280  			for _, assertion := range test.assertions {
   281  				var tk *openfgav1.CheckRequestTupleKey
   282  				if assertion.Tuple != nil {
   283  					tk = tuple.NewCheckRequestTupleKey(
   284  						assertion.Tuple.GetObject(),
   285  						assertion.Tuple.GetRelation(),
   286  						assertion.Tuple.GetUser(),
   287  					)
   288  				}
   289  
   290  				checkResp, err := client.Check(context.Background(), &openfgav1.CheckRequest{
   291  					StoreId:              storeID,
   292  					AuthorizationModelId: modelID,
   293  					TupleKey:             tk,
   294  					ContextualTuples: &openfgav1.ContextualTupleKeys{
   295  						TupleKeys: assertion.ContextualTuples,
   296  					},
   297  				})
   298  
   299  				if assertion.ErrorCode == 0 {
   300  					require.NoError(t, err)
   301  					require.Equal(t, assertion.Expectation, checkResp.GetAllowed())
   302  				} else {
   303  					require.Error(t, err)
   304  					e, ok := status.FromError(err)
   305  					require.True(t, ok)
   306  					require.Equal(t, assertion.ErrorCode, int(e.Code()))
   307  				}
   308  			}
   309  		})
   310  	}
   311  }
   312  
   313  func TestFunctionalGRPC(t *testing.T) {
   314  	// uncomment when https://github.com/hashicorp/go-retryablehttp/issues/214 is solved
   315  	// defer goleak.VerifyNone(t)
   316  	client := newOpenFGAServerAndClient(t)
   317  
   318  	t.Run("TestCreateStore", func(t *testing.T) { GRPCCreateStoreTest(t, client) })
   319  	t.Run("TestGetStore", func(t *testing.T) { GRPCGetStoreTest(t, client) })
   320  	t.Run("TestDeleteStore", func(t *testing.T) { GRPCDeleteStoreTest(t, client) })
   321  
   322  	t.Run("TestWrite", func(t *testing.T) { GRPCWriteTest(t, client) })
   323  	t.Run("TestRead", func(t *testing.T) { GRPCReadTest(t, client) })
   324  	t.Run("TestReadChanges", func(t *testing.T) { GRPCReadChangesTest(t, client) })
   325  
   326  	t.Run("TestCheck", func(t *testing.T) { GRPCCheckTest(t, client) })
   327  	t.Run("TestListObjects", func(t *testing.T) { GRPCListObjectsTest(t, client) })
   328  	t.Run("TestListUsersValidation", func(t *testing.T) { GRPCListUsersTest(t, client) })
   329  	t.Run("TestWriteAuthorizationModel", func(t *testing.T) { GRPCWriteAuthorizationModelTest(t, client) })
   330  	t.Run("TestReadAuthorizationModel", func(t *testing.T) { GRPCReadAuthorizationModelTest(t, client) })
   331  	t.Run("TestReadAuthorizationModels", func(t *testing.T) { GRPCReadAuthorizationModelsTest(t, client) })
   332  	t.Run("TestWriteAssertions", func(t *testing.T) { GRPCWriteAssertionsTest(t, client) })
   333  
   334  	t.Run("TestWriteAuthorizationModel", func(t *testing.T) { GRPCWriteAuthorizationModelTest(t, client) })
   335  	t.Run("TestReadAuthorizationModel", func(t *testing.T) { GRPCReadAuthorizationModelTest(t, client) })
   336  	t.Run("TestReadAuthorizationModels", func(t *testing.T) { GRPCReadAuthorizationModelsTest(t, client) })
   337  }
   338  
   339  func TestGRPCWithPresharedKey(t *testing.T) {
   340  	cfg := config.MustDefaultConfig()
   341  	cfg.Authn.Method = "preshared"
   342  	cfg.Authn.AuthnPresharedKeyConfig = &config.AuthnPresharedKeyConfig{Keys: []string{"key1", "key2"}}
   343  
   344  	StartServer(t, cfg)
   345  
   346  	conn := testutils.CreateGrpcConnection(t, cfg.GRPC.Addr)
   347  
   348  	testutils.EnsureServiceHealthy(t, cfg.GRPC.Addr, cfg.HTTP.Addr, nil, true)
   349  
   350  	openfgaClient := openfgav1.NewOpenFGAServiceClient(conn)
   351  
   352  	_, err := openfgaClient.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{
   353  		Name: "openfga-demo",
   354  	})
   355  	require.Error(t, err)
   356  
   357  	s, ok := status.FromError(err)
   358  	require.True(t, ok)
   359  	require.Equal(t, codes.Code(openfgav1.AuthErrorCode_bearer_token_missing), s.Code())
   360  
   361  	ctx := metadata.AppendToOutgoingContext(context.Background(), "authorization", "Bearer key1")
   362  	_, err = openfgaClient.CreateStore(ctx, &openfgav1.CreateStoreRequest{
   363  		Name: "openfga-demo1",
   364  	})
   365  	require.NoError(t, err)
   366  
   367  	ctx = metadata.AppendToOutgoingContext(context.Background(), "authorization", "Bearer key2")
   368  	_, err = openfgaClient.CreateStore(ctx, &openfgav1.CreateStoreRequest{
   369  		Name: "openfga-demo2",
   370  	})
   371  	require.NoError(t, err)
   372  
   373  	ctx = metadata.AppendToOutgoingContext(context.Background(), "authorization", "Bearer key3")
   374  	_, err = openfgaClient.CreateStore(ctx, &openfgav1.CreateStoreRequest{
   375  		Name: "openfga-demo3",
   376  	})
   377  	require.Error(t, err)
   378  
   379  	s, ok = status.FromError(err)
   380  	require.True(t, ok)
   381  	require.Equal(t, codes.Code(openfgav1.AuthErrorCode_unauthenticated), s.Code())
   382  }
   383  
   384  func GRPCWriteTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
   385  	type output struct {
   386  		errorCode    codes.Code
   387  		errorMessage string
   388  	}
   389  
   390  	resp, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{
   391  		Name: "openfga-demo",
   392  	})
   393  	require.NoError(t, err)
   394  	storeID := resp.GetId()
   395  
   396  	model := parser.MustTransformDSLToProto(`model
   397  	schema 1.1
   398  type user
   399  
   400  type document
   401    relations
   402  	define viewer: [user]
   403  `)
   404  
   405  	writeModelResp, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{
   406  		StoreId:         storeID,
   407  		TypeDefinitions: model.GetTypeDefinitions(),
   408  		Conditions:      model.GetConditions(),
   409  		SchemaVersion:   typesystem.SchemaVersion1_1,
   410  	})
   411  	require.NoError(t, err)
   412  	require.NotPanics(t, func() { ulid.MustParse(writeModelResp.GetAuthorizationModelId()) })
   413  	modelID := writeModelResp.GetAuthorizationModelId()
   414  
   415  	tests := []struct {
   416  		name   string
   417  		input  *openfgav1.WriteRequest
   418  		output output
   419  	}{
   420  		{
   421  			name: "happy_path_writes",
   422  			input: &openfgav1.WriteRequest{
   423  				StoreId:              storeID,
   424  				AuthorizationModelId: modelID,
   425  				Writes: &openfgav1.WriteRequestWrites{
   426  					TupleKeys: []*openfgav1.TupleKey{
   427  						{Object: "document:1", Relation: "viewer", User: "user:jon"},
   428  					},
   429  				},
   430  			},
   431  			output: output{
   432  				errorCode: codes.OK,
   433  			},
   434  		},
   435  		{
   436  			name: "happy_path_deletes",
   437  			input: &openfgav1.WriteRequest{
   438  				StoreId:              storeID,
   439  				AuthorizationModelId: modelID,
   440  				Deletes: &openfgav1.WriteRequestDeletes{
   441  					TupleKeys: []*openfgav1.TupleKeyWithoutCondition{
   442  						{
   443  							Object: "document:1", Relation: "viewer", User: "user:jon",
   444  						},
   445  					},
   446  				},
   447  			},
   448  			output: output{
   449  				errorCode: codes.OK,
   450  			},
   451  		},
   452  		{
   453  			name: "invalid_store_id",
   454  			input: &openfgav1.WriteRequest{
   455  				StoreId:              "invalid-store-id",
   456  				AuthorizationModelId: modelID,
   457  				Writes:               &openfgav1.WriteRequestWrites{},
   458  			},
   459  			output: output{
   460  				errorCode:    codes.InvalidArgument,
   461  				errorMessage: `value does not match regex pattern "^[ABCDEFGHJKMNPQRSTVWXYZ0-9]{26}$`,
   462  			},
   463  		},
   464  		{
   465  			name: "invalid_model_id",
   466  			input: &openfgav1.WriteRequest{
   467  				StoreId:              storeID,
   468  				AuthorizationModelId: "invalid-model-id",
   469  				Writes:               &openfgav1.WriteRequestWrites{},
   470  			},
   471  			output: output{
   472  				errorCode:    codes.InvalidArgument,
   473  				errorMessage: `value does not match regex pattern "^[ABCDEFGHJKMNPQRSTVWXYZ0-9]{26}$`,
   474  			},
   475  		},
   476  		{
   477  			name: "nil_writes_and_deletes",
   478  			input: &openfgav1.WriteRequest{
   479  				StoreId:              storeID,
   480  				AuthorizationModelId: modelID,
   481  			},
   482  			output: output{
   483  				errorCode:    codes.Code(2003),
   484  				errorMessage: `Invalid input. Make sure you provide at least one write, or at least one delete`,
   485  			},
   486  		},
   487  	}
   488  
   489  	for _, test := range tests {
   490  		t.Run(test.name, func(t *testing.T) {
   491  			_, err := client.Write(context.Background(), test.input)
   492  			s, ok := status.FromError(err)
   493  
   494  			require.True(t, ok)
   495  			require.Equal(t, test.output.errorCode.String(), s.Code().String())
   496  
   497  			if s.Code() == codes.OK {
   498  				require.NoError(t, err)
   499  			} else {
   500  				require.Contains(t, err.Error(), test.output.errorMessage)
   501  			}
   502  		})
   503  	}
   504  }
   505  
   506  func GRPCReadTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
   507  
   508  }
   509  
   510  func GRPCReadChangesTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
   511  
   512  }
   513  
   514  func GRPCCreateStoreTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
   515  	type output struct {
   516  		errorCode codes.Code
   517  	}
   518  
   519  	tests := []struct {
   520  		name   string
   521  		input  *openfgav1.CreateStoreRequest
   522  		output output
   523  	}{
   524  		{
   525  			name:  "empty_request",
   526  			input: &openfgav1.CreateStoreRequest{},
   527  			output: output{
   528  				errorCode: codes.InvalidArgument,
   529  			},
   530  		},
   531  		{
   532  			name: "invalid_name_length",
   533  			input: &openfgav1.CreateStoreRequest{
   534  				Name: "a",
   535  			},
   536  			output: output{
   537  				errorCode: codes.InvalidArgument,
   538  			},
   539  		},
   540  		{
   541  			name: "invalid_name_characters",
   542  			input: &openfgav1.CreateStoreRequest{
   543  				Name: "$openfga",
   544  			},
   545  			output: output{
   546  				errorCode: codes.InvalidArgument,
   547  			},
   548  		},
   549  		{
   550  			name: "success",
   551  			input: &openfgav1.CreateStoreRequest{
   552  				Name: "openfga",
   553  			},
   554  		},
   555  		{
   556  			name: "duplicate_store_name_is_allowed",
   557  			input: &openfgav1.CreateStoreRequest{
   558  				Name: "openfga",
   559  			},
   560  		},
   561  	}
   562  
   563  	for _, test := range tests {
   564  		t.Run(test.name, func(t *testing.T) {
   565  			response, err := client.CreateStore(context.Background(), test.input)
   566  
   567  			s, ok := status.FromError(err)
   568  			require.True(t, ok)
   569  			require.Equal(t, test.output.errorCode.String(), s.Code().String())
   570  
   571  			if test.output.errorCode == codes.OK {
   572  				require.Equal(t, test.input.GetName(), response.GetName())
   573  				_, err = ulid.Parse(response.GetId())
   574  				require.NoError(t, err)
   575  			}
   576  		})
   577  	}
   578  }
   579  
   580  func GRPCGetStoreTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
   581  	resp1, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{
   582  		Name: "openfga-demo",
   583  	})
   584  	require.NoError(t, err)
   585  
   586  	resp2, err := client.GetStore(context.Background(), &openfgav1.GetStoreRequest{
   587  		StoreId: resp1.GetId(),
   588  	})
   589  	require.NoError(t, err)
   590  
   591  	require.Equal(t, resp1.GetName(), resp2.GetName())
   592  	require.Equal(t, resp1.GetId(), resp2.GetId())
   593  
   594  	resp3, err := client.GetStore(context.Background(), &openfgav1.GetStoreRequest{
   595  		StoreId: ulid.Make().String(),
   596  	})
   597  	require.Error(t, err)
   598  	require.Nil(t, resp3)
   599  }
   600  
   601  func TestGRPCListStores(t *testing.T) {
   602  	client := newOpenFGAServerAndClient(t)
   603  	_, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{
   604  		Name: "openfga-demo",
   605  	})
   606  	require.NoError(t, err)
   607  
   608  	_, err = client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{
   609  		Name: "openfga-test",
   610  	})
   611  	require.NoError(t, err)
   612  
   613  	response1, err := client.ListStores(context.Background(), &openfgav1.ListStoresRequest{
   614  		PageSize: wrapperspb.Int32(1),
   615  	})
   616  	require.NoError(t, err)
   617  
   618  	require.NotEmpty(t, response1.GetContinuationToken())
   619  
   620  	var received []*openfgav1.Store
   621  	received = append(received, response1.GetStores()...)
   622  
   623  	response2, err := client.ListStores(context.Background(), &openfgav1.ListStoresRequest{
   624  		PageSize:          wrapperspb.Int32(2),
   625  		ContinuationToken: response1.GetContinuationToken(),
   626  	})
   627  	require.NoError(t, err)
   628  
   629  	require.Empty(t, response2.GetContinuationToken())
   630  
   631  	received = append(received, response2.GetStores()...)
   632  
   633  	require.Len(t, received, 2)
   634  	// todo: add assertions on received Store objects
   635  }
   636  
   637  func GRPCDeleteStoreTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
   638  	response1, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{
   639  		Name: "openfga-demo",
   640  	})
   641  	require.NoError(t, err)
   642  
   643  	response2, err := client.GetStore(context.Background(), &openfgav1.GetStoreRequest{
   644  		StoreId: response1.GetId(),
   645  	})
   646  	require.NoError(t, err)
   647  
   648  	require.Equal(t, response1.GetId(), response2.GetId())
   649  
   650  	_, err = client.DeleteStore(context.Background(), &openfgav1.DeleteStoreRequest{
   651  		StoreId: response1.GetId(),
   652  	})
   653  	require.NoError(t, err)
   654  
   655  	response3, err := client.GetStore(context.Background(), &openfgav1.GetStoreRequest{
   656  		StoreId: response1.GetId(),
   657  	})
   658  	require.Nil(t, response3)
   659  
   660  	s, ok := status.FromError(err)
   661  	require.True(t, ok)
   662  	require.Equal(t, codes.Code(openfgav1.NotFoundErrorCode_store_id_not_found), s.Code())
   663  
   664  	// delete is idempotent, so if the store does not exist it's a noop
   665  	_, err = client.DeleteStore(context.Background(), &openfgav1.DeleteStoreRequest{
   666  		StoreId: ulid.Make().String(),
   667  	})
   668  	require.NoError(t, err)
   669  }
   670  
   671  func GRPCCheckTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
   672  	type output struct {
   673  		errorCode codes.Code
   674  	}
   675  
   676  	tests := []struct {
   677  		name   string
   678  		input  *openfgav1.CheckRequest
   679  		output output
   680  	}{
   681  		{
   682  			name:  "empty_request",
   683  			input: &openfgav1.CheckRequest{},
   684  			output: output{
   685  				errorCode: codes.InvalidArgument,
   686  			},
   687  		},
   688  		{
   689  			name: "invalid_storeID_because_too_short",
   690  			input: &openfgav1.CheckRequest{
   691  				StoreId:              "1",
   692  				AuthorizationModelId: ulid.Make().String(),
   693  				TupleKey:             tuple.NewCheckRequestTupleKey("document:doc1", "viewer", "bob"),
   694  			},
   695  			output: output{
   696  				errorCode: codes.InvalidArgument,
   697  			},
   698  		},
   699  		{
   700  			name: "invalid_storeID_because_extra_chars",
   701  			input: &openfgav1.CheckRequest{
   702  				StoreId:              ulid.Make().String() + "A",
   703  				AuthorizationModelId: ulid.Make().String(),
   704  				TupleKey:             tuple.NewCheckRequestTupleKey("document:doc1", "viewer", "bob"),
   705  			},
   706  			output: output{
   707  				errorCode: codes.InvalidArgument,
   708  			},
   709  		},
   710  		{
   711  			name: "invalid_storeID_because_invalid_chars",
   712  			input: &openfgav1.CheckRequest{
   713  				StoreId:              "ABCDEFGHIJKLMNOPQRSTUVWXY@",
   714  				AuthorizationModelId: ulid.Make().String(),
   715  				TupleKey:             tuple.NewCheckRequestTupleKey("document:doc1", "viewer", "bob"),
   716  			},
   717  			output: output{
   718  				errorCode: codes.InvalidArgument,
   719  			},
   720  		},
   721  		{
   722  			name: "invalid_authorization_model_ID_because_extra_chars",
   723  			input: &openfgav1.CheckRequest{
   724  				StoreId:              ulid.Make().String(),
   725  				AuthorizationModelId: ulid.Make().String() + "A",
   726  				TupleKey:             tuple.NewCheckRequestTupleKey("document:doc1", "viewer", "bob"),
   727  			},
   728  			output: output{
   729  				errorCode: codes.InvalidArgument,
   730  			},
   731  		},
   732  		{
   733  			name: "invalid_authorization_model_ID_because_invalid_chars",
   734  			input: &openfgav1.CheckRequest{
   735  				StoreId:              ulid.Make().String(),
   736  				AuthorizationModelId: "ABCDEFGHIJKLMNOPQRSTUVWXY@",
   737  				TupleKey:             tuple.NewCheckRequestTupleKey("document:doc1", "viewer", "bob"),
   738  			},
   739  			output: output{
   740  				errorCode: codes.InvalidArgument,
   741  			},
   742  		},
   743  		{
   744  			name: "missing_tuplekey_field",
   745  			input: &openfgav1.CheckRequest{
   746  				StoreId:              ulid.Make().String(),
   747  				AuthorizationModelId: ulid.Make().String(),
   748  			},
   749  			output: output{
   750  				errorCode: codes.InvalidArgument,
   751  			},
   752  		},
   753  		{
   754  			name: "missing_user",
   755  			input: &openfgav1.CheckRequest{
   756  				StoreId:              ulid.Make().String(),
   757  				AuthorizationModelId: ulid.Make().String(),
   758  				TupleKey: &openfgav1.CheckRequestTupleKey{
   759  					Relation: "relation",
   760  					Object:   "obj:1",
   761  				},
   762  			},
   763  			output: output{
   764  				errorCode: codes.InvalidArgument,
   765  			},
   766  		},
   767  		{
   768  			name: "missing_relation",
   769  			input: &openfgav1.CheckRequest{
   770  				StoreId:              ulid.Make().String(),
   771  				AuthorizationModelId: ulid.Make().String(),
   772  				TupleKey: &openfgav1.CheckRequestTupleKey{
   773  					User:   "user:anne",
   774  					Object: "obj:1",
   775  				},
   776  			},
   777  			output: output{
   778  				errorCode: codes.InvalidArgument,
   779  			},
   780  		},
   781  		{
   782  			name: "missing_object",
   783  			input: &openfgav1.CheckRequest{
   784  				StoreId:              ulid.Make().String(),
   785  				AuthorizationModelId: ulid.Make().String(),
   786  				TupleKey: &openfgav1.CheckRequestTupleKey{
   787  					User:     "user:anne",
   788  					Relation: "relation",
   789  				},
   790  			},
   791  			output: output{
   792  				errorCode: codes.InvalidArgument,
   793  			},
   794  		},
   795  		{
   796  			name: "model_not_found",
   797  			input: &openfgav1.CheckRequest{
   798  				StoreId:              ulid.Make().String(),
   799  				AuthorizationModelId: ulid.Make().String(),
   800  				TupleKey: &openfgav1.CheckRequestTupleKey{
   801  					User:     "user:anne",
   802  					Object:   "obj:1",
   803  					Relation: "relation",
   804  				},
   805  			},
   806  			output: output{
   807  				errorCode: 2001, // ErrorCode_authorization_model_not_found
   808  			},
   809  		},
   810  	}
   811  
   812  	for _, test := range tests {
   813  		t.Run(test.name, func(t *testing.T) {
   814  			_, err := client.Check(context.Background(), test.input)
   815  
   816  			s, ok := status.FromError(err)
   817  			require.True(t, ok)
   818  			require.Equal(t, test.output.errorCode.String(), s.Code().String())
   819  
   820  			if s.Code() == codes.OK {
   821  				require.NoError(t, err)
   822  			} else {
   823  				require.Error(t, err)
   824  			}
   825  		})
   826  	}
   827  }
   828  
   829  func GRPCListObjectsTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
   830  	type output struct {
   831  		errorCode codes.Code
   832  	}
   833  
   834  	tests := []struct {
   835  		name   string
   836  		input  *openfgav1.ListObjectsRequest
   837  		output output
   838  	}{
   839  		{
   840  			name: "undefined_model_id_returns_error",
   841  			input: &openfgav1.ListObjectsRequest{
   842  				StoreId:              ulid.Make().String(),
   843  				AuthorizationModelId: ulid.Make().String(), // generate random ulid so it doesn't match
   844  				Type:                 "document",
   845  				Relation:             "viewer",
   846  				User:                 "user:jon",
   847  			},
   848  			output: output{
   849  				errorCode: codes.Code(openfgav1.ErrorCode_authorization_model_not_found),
   850  			},
   851  		},
   852  		{
   853  			name:  "empty_request",
   854  			input: &openfgav1.ListObjectsRequest{},
   855  			output: output{
   856  				errorCode: codes.InvalidArgument,
   857  			},
   858  		},
   859  		{
   860  			name: "invalid_storeID_because_too_short",
   861  			input: &openfgav1.ListObjectsRequest{
   862  				StoreId:              "1",
   863  				AuthorizationModelId: ulid.Make().String(),
   864  				Type:                 "document",
   865  				Relation:             "viewer",
   866  				User:                 "user:jon",
   867  			},
   868  			output: output{
   869  				errorCode: codes.InvalidArgument,
   870  			},
   871  		},
   872  		{
   873  			name: "invalid_storeID_because_extra_chars",
   874  			input: &openfgav1.ListObjectsRequest{
   875  				StoreId:              ulid.Make().String() + "A",
   876  				AuthorizationModelId: ulid.Make().String(),
   877  				Type:                 "document",
   878  				Relation:             "viewer",
   879  				User:                 "user:jon",
   880  			},
   881  			output: output{
   882  				errorCode: codes.InvalidArgument,
   883  			},
   884  		},
   885  		{
   886  			name: "invalid_storeID_because_invalid_chars",
   887  			input: &openfgav1.ListObjectsRequest{
   888  				StoreId:              "ABCDEFGHIJKLMNOPQRSTUVWXY@",
   889  				AuthorizationModelId: ulid.Make().String(),
   890  				Type:                 "document",
   891  				Relation:             "viewer",
   892  				User:                 "user:jon",
   893  			},
   894  			output: output{
   895  				errorCode: codes.InvalidArgument,
   896  			},
   897  		},
   898  		{
   899  			name: "invalid_authorization_model_ID_because_extra_chars",
   900  			input: &openfgav1.ListObjectsRequest{
   901  				StoreId:              ulid.Make().String(),
   902  				AuthorizationModelId: ulid.Make().String() + "A",
   903  				Type:                 "document",
   904  				Relation:             "viewer",
   905  				User:                 "user:jon",
   906  			},
   907  			output: output{
   908  				errorCode: codes.InvalidArgument,
   909  			},
   910  		},
   911  		{
   912  			name: "invalid_authorization_model_ID_because_invalid_chars",
   913  			input: &openfgav1.ListObjectsRequest{
   914  				StoreId:              ulid.Make().String(),
   915  				AuthorizationModelId: "ABCDEFGHIJKLMNOPQRSTUVWXY@",
   916  				Type:                 "document",
   917  				Relation:             "viewer",
   918  				User:                 "user:jon",
   919  			},
   920  			output: output{
   921  				errorCode: codes.InvalidArgument,
   922  			},
   923  		},
   924  		{
   925  			name: "missing_user",
   926  			input: &openfgav1.ListObjectsRequest{
   927  				StoreId:              ulid.Make().String(),
   928  				AuthorizationModelId: ulid.Make().String(),
   929  				Type:                 "document",
   930  				Relation:             "viewer",
   931  			},
   932  			output: output{
   933  				errorCode: codes.InvalidArgument,
   934  			},
   935  		},
   936  		{
   937  			name: "missing_relation",
   938  			input: &openfgav1.ListObjectsRequest{
   939  				StoreId:              ulid.Make().String(),
   940  				AuthorizationModelId: ulid.Make().String(),
   941  				Type:                 "document",
   942  				User:                 "user:jon",
   943  			},
   944  			output: output{
   945  				errorCode: codes.InvalidArgument,
   946  			},
   947  		},
   948  		{
   949  			name: "missing_type",
   950  			input: &openfgav1.ListObjectsRequest{
   951  				StoreId:              ulid.Make().String(),
   952  				AuthorizationModelId: ulid.Make().String(),
   953  				Relation:             "viewer",
   954  				User:                 "user:jon",
   955  			},
   956  			output: output{
   957  				errorCode: codes.InvalidArgument,
   958  			},
   959  		},
   960  		{
   961  			name: "model_not_found",
   962  			input: &openfgav1.ListObjectsRequest{
   963  				StoreId:              ulid.Make().String(),
   964  				AuthorizationModelId: ulid.Make().String(),
   965  				Type:                 "document",
   966  				Relation:             "viewer",
   967  				User:                 "user:jon",
   968  			},
   969  			output: output{
   970  				errorCode: 2001, // ErrorCode_authorization_model_not_found
   971  			},
   972  		},
   973  	}
   974  
   975  	for _, test := range tests {
   976  		t.Run(test.name, func(t *testing.T) {
   977  			_, err := client.ListObjects(context.Background(), test.input)
   978  
   979  			s, ok := status.FromError(err)
   980  			require.True(t, ok)
   981  			require.Equal(t, test.output.errorCode.String(), s.Code().String())
   982  
   983  			if s.Code() == codes.OK {
   984  				require.NoError(t, err)
   985  			} else {
   986  				require.Error(t, err)
   987  			}
   988  		})
   989  	}
   990  }
   991  
   992  func GRPCListUsersTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
   993  	tests := []struct {
   994  		name              string
   995  		input             *openfgav1.ListUsersRequest
   996  		expectedErrorCode codes.Code
   997  	}{
   998  		{
   999  			name: "too_many_user_filters",
  1000  			input: &openfgav1.ListUsersRequest{
  1001  				StoreId:              ulid.Make().String(),
  1002  				AuthorizationModelId: ulid.Make().String(),
  1003  				Relation:             "viewer",
  1004  				Object: &openfgav1.Object{
  1005  					Type: "document",
  1006  					Id:   "1",
  1007  				},
  1008  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}, {Type: "employee"}},
  1009  			},
  1010  			expectedErrorCode: codes.InvalidArgument,
  1011  		},
  1012  		{
  1013  			name: "zero_user_filters",
  1014  			input: &openfgav1.ListUsersRequest{
  1015  				StoreId:              ulid.Make().String(),
  1016  				AuthorizationModelId: ulid.Make().String(),
  1017  				Relation:             "viewer",
  1018  				Object: &openfgav1.Object{
  1019  					Type: "document",
  1020  					Id:   "1",
  1021  				},
  1022  				UserFilters: []*openfgav1.UserTypeFilter{},
  1023  			},
  1024  			expectedErrorCode: codes.InvalidArgument,
  1025  		},
  1026  		{
  1027  			name: "object_no_type_defined",
  1028  			input: &openfgav1.ListUsersRequest{
  1029  				StoreId:              ulid.Make().String(),
  1030  				AuthorizationModelId: ulid.Make().String(),
  1031  				Relation:             "viewer",
  1032  				Object: &openfgav1.Object{
  1033  					Id: "1",
  1034  				},
  1035  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  1036  			},
  1037  			expectedErrorCode: codes.InvalidArgument,
  1038  		},
  1039  		{
  1040  			name: "object_no_id_defined",
  1041  			input: &openfgav1.ListUsersRequest{
  1042  				StoreId:              ulid.Make().String(),
  1043  				AuthorizationModelId: ulid.Make().String(),
  1044  				Relation:             "viewer",
  1045  				Object: &openfgav1.Object{
  1046  					Type: "user",
  1047  				},
  1048  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  1049  			},
  1050  			expectedErrorCode: codes.InvalidArgument,
  1051  		},
  1052  		{
  1053  			name:              "empty_request",
  1054  			input:             &openfgav1.ListUsersRequest{},
  1055  			expectedErrorCode: codes.InvalidArgument,
  1056  		},
  1057  		{
  1058  			name: "invalid_storeID_because_too_short",
  1059  			input: &openfgav1.ListUsersRequest{
  1060  				StoreId:              "1",
  1061  				AuthorizationModelId: ulid.Make().String(),
  1062  				Relation:             "viewer",
  1063  				Object: &openfgav1.Object{
  1064  					Type: "document",
  1065  					Id:   "1",
  1066  				},
  1067  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  1068  			},
  1069  			expectedErrorCode: codes.InvalidArgument,
  1070  		},
  1071  		{
  1072  			name: "invalid_storeID_because_extra_chars",
  1073  			input: &openfgav1.ListUsersRequest{
  1074  				StoreId:              ulid.Make().String() + "A",
  1075  				AuthorizationModelId: ulid.Make().String(),
  1076  				Relation:             "viewer",
  1077  				Object: &openfgav1.Object{
  1078  					Type: "document",
  1079  					Id:   "1",
  1080  				},
  1081  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  1082  			},
  1083  			expectedErrorCode: codes.InvalidArgument,
  1084  		},
  1085  		{
  1086  			name: "invalid_storeID_because_invalid_chars",
  1087  			input: &openfgav1.ListUsersRequest{
  1088  				StoreId:              "ABCDEFGHIJKLMNOPQRSTUVWXY@",
  1089  				AuthorizationModelId: ulid.Make().String(),
  1090  				Relation:             "viewer",
  1091  				Object: &openfgav1.Object{
  1092  					Type: "document",
  1093  					Id:   "1",
  1094  				},
  1095  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  1096  			},
  1097  			expectedErrorCode: codes.InvalidArgument,
  1098  		},
  1099  		{
  1100  			name: "invalid_authorization_model_ID_because_extra_chars",
  1101  			input: &openfgav1.ListUsersRequest{
  1102  				StoreId:              ulid.Make().String(),
  1103  				AuthorizationModelId: ulid.Make().String() + "A",
  1104  				Relation:             "viewer",
  1105  				Object: &openfgav1.Object{
  1106  					Type: "document",
  1107  					Id:   "1",
  1108  				},
  1109  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  1110  			},
  1111  			expectedErrorCode: codes.InvalidArgument,
  1112  		},
  1113  		{
  1114  			name: "invalid_store_ID_because_extra_chars",
  1115  			input: &openfgav1.ListUsersRequest{
  1116  				StoreId:              ulid.Make().String() + "A",
  1117  				AuthorizationModelId: ulid.Make().String(),
  1118  				Relation:             "viewer",
  1119  				Object: &openfgav1.Object{
  1120  					Type: "document",
  1121  					Id:   "1",
  1122  				},
  1123  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  1124  			},
  1125  			expectedErrorCode: codes.InvalidArgument,
  1126  		},
  1127  		{
  1128  			name: "invalid_authorization_model_ID_because_invalid_chars",
  1129  			input: &openfgav1.ListUsersRequest{
  1130  				StoreId:              ulid.Make().String(),
  1131  				AuthorizationModelId: "ABCDEFGHIJKLMNOPQRSTUVWXY@",
  1132  				Relation:             "viewer",
  1133  				Object: &openfgav1.Object{
  1134  					Type: "document",
  1135  					Id:   "1",
  1136  				},
  1137  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  1138  			},
  1139  			expectedErrorCode: codes.InvalidArgument,
  1140  		},
  1141  		{
  1142  			name: "invalid_authorization_model_ID_because_invalid_chars",
  1143  			input: &openfgav1.ListUsersRequest{
  1144  				StoreId:              "ABCDEFGHIJKLMNOPQRSTUVWXY@",
  1145  				AuthorizationModelId: ulid.Make().String(),
  1146  				Relation:             "viewer",
  1147  				Object: &openfgav1.Object{
  1148  					Type: "document",
  1149  					Id:   "1",
  1150  				},
  1151  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  1152  			},
  1153  			expectedErrorCode: codes.InvalidArgument,
  1154  		},
  1155  		{
  1156  			name: "missing_object",
  1157  			input: &openfgav1.ListUsersRequest{
  1158  				StoreId:              ulid.Make().String(),
  1159  				AuthorizationModelId: ulid.Make().String(),
  1160  				Relation:             "viewer",
  1161  				UserFilters:          []*openfgav1.UserTypeFilter{{Type: "user"}},
  1162  			},
  1163  			expectedErrorCode: codes.InvalidArgument,
  1164  		},
  1165  		{
  1166  			name: "empty_object",
  1167  			input: &openfgav1.ListUsersRequest{
  1168  				StoreId:              ulid.Make().String(),
  1169  				AuthorizationModelId: ulid.Make().String(),
  1170  				Relation:             "viewer",
  1171  				Object:               &openfgav1.Object{},
  1172  				UserFilters:          []*openfgav1.UserTypeFilter{{Type: "user"}},
  1173  			},
  1174  			expectedErrorCode: codes.InvalidArgument,
  1175  		},
  1176  		{
  1177  			name: "missing_relation",
  1178  			input: &openfgav1.ListUsersRequest{
  1179  				StoreId:              ulid.Make().String(),
  1180  				AuthorizationModelId: ulid.Make().String(),
  1181  				Object: &openfgav1.Object{
  1182  					Type: "document",
  1183  					Id:   "1",
  1184  				},
  1185  				UserFilters: []*openfgav1.UserTypeFilter{{Type: "user"}},
  1186  			},
  1187  			expectedErrorCode: codes.InvalidArgument,
  1188  		},
  1189  	}
  1190  
  1191  	for _, test := range tests {
  1192  		t.Run(test.name, func(t *testing.T) {
  1193  			_, err := client.ListUsers(context.Background(), test.input)
  1194  
  1195  			s, ok := status.FromError(err)
  1196  			require.True(t, ok)
  1197  			require.Equal(t, test.expectedErrorCode, s.Code())
  1198  
  1199  			if s.Code() == codes.OK {
  1200  				require.NoError(t, err)
  1201  			} else {
  1202  				require.Error(t, err)
  1203  			}
  1204  		})
  1205  	}
  1206  }
  1207  
  1208  // TestExpandWorkflows are tests that involve workflows that define assertions for
  1209  // Expands against multi-model stores etc..
  1210  // TODO move to consolidated_1_1_tests.yaml.
  1211  func TestExpandWorkflows(t *testing.T) {
  1212  	client := newOpenFGAServerAndClient(t)
  1213  
  1214  	/*
  1215  	 * TypedWildcardsFromOtherModelsIgnored ensures that a typed wildcard introduced
  1216  	 * from a prior model does not impact the Expand outcome of a model that should not
  1217  	 * involve it. For example,
  1218  	 *
  1219  	 * type user
  1220  	 * type document
  1221  	 *   relations
  1222  	 *     define viewer: [user, user:*]
  1223  	 *
  1224  	 * write(document:1#viewer@user:*)
  1225  	 * write(document:1#viewer@user:jon)
  1226  	 * Expand(document:1#viewer) --> {tree: {root: {name: document:1#viewer, leaf: {users: [user:*, user:jon]}}}}
  1227  	 *
  1228  	 * type user
  1229  	 * type document
  1230  	 *	relations
  1231  	 *	  define viewer: [user]
  1232  	 *
  1233  	 * Expand(document:1#viewer) --> {tree: {root: {name: document:1#viewer, leaf: {users: [user:jon]}}}}
  1234  	 *
  1235  	 * type employee
  1236  	 * type document
  1237  	 *   relations
  1238  	 *     define viewer: [employee]
  1239  	 *
  1240  	 * type user
  1241  	 * type employee
  1242  	 * type document
  1243  	 *   relations
  1244  	 *     define viewer: [employee]
  1245  	 * Expand(document:1#viewer) --> {tree: {root: {name: document:1#viewer, leaf: {users: []}}}}
  1246  	 */
  1247  	t.Run("TypedWildcardsFromOtherModelsIgnored", func(t *testing.T) {
  1248  		resp1, err := client.CreateStore(context.Background(), &openfgav1.CreateStoreRequest{
  1249  			Name: "openfga-demo",
  1250  		})
  1251  		require.NoError(t, err)
  1252  
  1253  		storeID := resp1.GetId()
  1254  
  1255  		_, err = client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{
  1256  			StoreId:       storeID,
  1257  			SchemaVersion: typesystem.SchemaVersion1_1,
  1258  			TypeDefinitions: []*openfgav1.TypeDefinition{
  1259  				{
  1260  					Type: "user",
  1261  				},
  1262  				{
  1263  					Type: "document",
  1264  					Relations: map[string]*openfgav1.Userset{
  1265  						"viewer": typesystem.This(),
  1266  					},
  1267  					Metadata: &openfgav1.Metadata{
  1268  						Relations: map[string]*openfgav1.RelationMetadata{
  1269  							"viewer": {
  1270  								DirectlyRelatedUserTypes: []*openfgav1.RelationReference{
  1271  									typesystem.DirectRelationReference("user", ""),
  1272  									typesystem.WildcardRelationReference("user"),
  1273  								},
  1274  							},
  1275  						},
  1276  					},
  1277  				},
  1278  			},
  1279  		})
  1280  		require.NoError(t, err)
  1281  
  1282  		_, err = client.Write(context.Background(), &openfgav1.WriteRequest{
  1283  			StoreId: storeID,
  1284  			Writes: &openfgav1.WriteRequestWrites{
  1285  				TupleKeys: []*openfgav1.TupleKey{
  1286  					{Object: "document:1", Relation: "viewer", User: "user:*"},
  1287  					{Object: "document:1", Relation: "viewer", User: "user:jon"},
  1288  				},
  1289  			},
  1290  		})
  1291  		require.NoError(t, err)
  1292  
  1293  		expandResp, err := client.Expand(context.Background(), &openfgav1.ExpandRequest{
  1294  			StoreId:  storeID,
  1295  			TupleKey: tuple.NewExpandRequestTupleKey("document:1", "viewer"),
  1296  		})
  1297  		require.NoError(t, err)
  1298  
  1299  		if diff := cmp.Diff(&openfgav1.UsersetTree{
  1300  			Root: &openfgav1.UsersetTree_Node{
  1301  				Name: "document:1#viewer",
  1302  				Value: &openfgav1.UsersetTree_Node_Leaf{
  1303  					Leaf: &openfgav1.UsersetTree_Leaf{
  1304  						Value: &openfgav1.UsersetTree_Leaf_Users{
  1305  							Users: &openfgav1.UsersetTree_Users{
  1306  								Users: []string{"user:*", "user:jon"},
  1307  							},
  1308  						},
  1309  					},
  1310  				},
  1311  			},
  1312  		}, expandResp.GetTree(), protocmp.Transform(), protocmp.SortRepeated(func(x, y string) bool {
  1313  			return x <= y
  1314  		})); diff != "" {
  1315  			require.Fail(t, diff)
  1316  		}
  1317  
  1318  		_, err = client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{
  1319  			StoreId:       storeID,
  1320  			SchemaVersion: typesystem.SchemaVersion1_1,
  1321  			TypeDefinitions: []*openfgav1.TypeDefinition{
  1322  				{
  1323  					Type: "user",
  1324  				},
  1325  				{
  1326  					Type: "document",
  1327  					Relations: map[string]*openfgav1.Userset{
  1328  						"viewer": typesystem.This(),
  1329  					},
  1330  					Metadata: &openfgav1.Metadata{
  1331  						Relations: map[string]*openfgav1.RelationMetadata{
  1332  							"viewer": {
  1333  								DirectlyRelatedUserTypes: []*openfgav1.RelationReference{
  1334  									typesystem.DirectRelationReference("user", ""),
  1335  								},
  1336  							},
  1337  						},
  1338  					},
  1339  				},
  1340  			},
  1341  		})
  1342  		require.NoError(t, err)
  1343  
  1344  		expandResp, err = client.Expand(context.Background(), &openfgav1.ExpandRequest{
  1345  			StoreId:  storeID,
  1346  			TupleKey: tuple.NewExpandRequestTupleKey("document:1", "viewer"),
  1347  		})
  1348  		require.NoError(t, err)
  1349  
  1350  		if diff := cmp.Diff(&openfgav1.UsersetTree{
  1351  			Root: &openfgav1.UsersetTree_Node{
  1352  				Name: "document:1#viewer",
  1353  				Value: &openfgav1.UsersetTree_Node_Leaf{
  1354  					Leaf: &openfgav1.UsersetTree_Leaf{
  1355  						Value: &openfgav1.UsersetTree_Leaf_Users{
  1356  							Users: &openfgav1.UsersetTree_Users{
  1357  								Users: []string{"user:jon"},
  1358  							},
  1359  						},
  1360  					},
  1361  				},
  1362  			},
  1363  		}, expandResp.GetTree(), protocmp.Transform()); diff != "" {
  1364  			require.Fail(t, diff)
  1365  		}
  1366  
  1367  		_, err = client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{
  1368  			StoreId:       storeID,
  1369  			SchemaVersion: typesystem.SchemaVersion1_1,
  1370  			TypeDefinitions: []*openfgav1.TypeDefinition{
  1371  				{
  1372  					Type: "employee",
  1373  				},
  1374  				{
  1375  					Type: "document",
  1376  					Relations: map[string]*openfgav1.Userset{
  1377  						"viewer": typesystem.This(),
  1378  					},
  1379  					Metadata: &openfgav1.Metadata{
  1380  						Relations: map[string]*openfgav1.RelationMetadata{
  1381  							"viewer": {
  1382  								DirectlyRelatedUserTypes: []*openfgav1.RelationReference{
  1383  									{Type: "employee"},
  1384  								},
  1385  							},
  1386  						},
  1387  					},
  1388  				},
  1389  			},
  1390  		})
  1391  		require.NoError(t, err)
  1392  
  1393  		expandResp, err = client.Expand(context.Background(), &openfgav1.ExpandRequest{
  1394  			StoreId:  storeID,
  1395  			TupleKey: tuple.NewExpandRequestTupleKey("document:1", "viewer"),
  1396  		})
  1397  		require.NoError(t, err)
  1398  
  1399  		if diff := cmp.Diff(&openfgav1.UsersetTree{
  1400  			Root: &openfgav1.UsersetTree_Node{
  1401  				Name: "document:1#viewer",
  1402  				Value: &openfgav1.UsersetTree_Node_Leaf{
  1403  					Leaf: &openfgav1.UsersetTree_Leaf{
  1404  						Value: &openfgav1.UsersetTree_Leaf_Users{
  1405  							Users: &openfgav1.UsersetTree_Users{
  1406  								Users: []string{},
  1407  							},
  1408  						},
  1409  					},
  1410  				},
  1411  			},
  1412  		}, expandResp.GetTree(), protocmp.Transform()); diff != "" {
  1413  			require.Fail(t, diff)
  1414  		}
  1415  
  1416  		_, err = client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{
  1417  			StoreId:       storeID,
  1418  			SchemaVersion: typesystem.SchemaVersion1_1,
  1419  			TypeDefinitions: []*openfgav1.TypeDefinition{
  1420  				{
  1421  					Type: "user",
  1422  				},
  1423  				{
  1424  					Type: "employee",
  1425  				},
  1426  				{
  1427  					Type: "document",
  1428  					Relations: map[string]*openfgav1.Userset{
  1429  						"viewer": typesystem.This(),
  1430  					},
  1431  					Metadata: &openfgav1.Metadata{
  1432  						Relations: map[string]*openfgav1.RelationMetadata{
  1433  							"viewer": {
  1434  								DirectlyRelatedUserTypes: []*openfgav1.RelationReference{
  1435  									{Type: "employee"},
  1436  								},
  1437  							},
  1438  						},
  1439  					},
  1440  				},
  1441  			},
  1442  		})
  1443  		require.NoError(t, err)
  1444  
  1445  		expandResp, err = client.Expand(context.Background(), &openfgav1.ExpandRequest{
  1446  			StoreId:  storeID,
  1447  			TupleKey: tuple.NewExpandRequestTupleKey("document:1", "viewer"),
  1448  		})
  1449  		require.NoError(t, err)
  1450  
  1451  		if diff := cmp.Diff(&openfgav1.UsersetTree{
  1452  			Root: &openfgav1.UsersetTree_Node{
  1453  				Name: "document:1#viewer",
  1454  				Value: &openfgav1.UsersetTree_Node_Leaf{
  1455  					Leaf: &openfgav1.UsersetTree_Leaf{
  1456  						Value: &openfgav1.UsersetTree_Leaf_Users{
  1457  							Users: &openfgav1.UsersetTree_Users{
  1458  								Users: []string{},
  1459  							},
  1460  						},
  1461  					},
  1462  				},
  1463  			},
  1464  		}, expandResp.GetTree(), protocmp.Transform()); diff != "" {
  1465  			require.Fail(t, diff)
  1466  		}
  1467  	})
  1468  }
  1469  
  1470  func GRPCReadAuthorizationModelTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
  1471  	type testData struct {
  1472  		model string
  1473  	}
  1474  
  1475  	type output struct {
  1476  		errorCode codes.Code
  1477  	}
  1478  
  1479  	tests := []struct {
  1480  		name     string
  1481  		input    *openfgav1.ReadAuthorizationModelRequest
  1482  		output   output
  1483  		testData *testData
  1484  	}{
  1485  		{
  1486  			name: "happy_path",
  1487  			testData: &testData{
  1488  				model: `model
  1489  	schema 1.1
  1490  type user`,
  1491  			},
  1492  			input: &openfgav1.ReadAuthorizationModelRequest{
  1493  				StoreId: ulid.Make().String(),
  1494  				Id:      ulid.Make().String(),
  1495  			},
  1496  			output: output{
  1497  				errorCode: codes.OK,
  1498  			},
  1499  		},
  1500  		{
  1501  			name:  "empty_request",
  1502  			input: &openfgav1.ReadAuthorizationModelRequest{},
  1503  			output: output{
  1504  				errorCode: codes.InvalidArgument,
  1505  			},
  1506  		},
  1507  		{
  1508  			name: "invalid_storeID_because_too_short",
  1509  			input: &openfgav1.ReadAuthorizationModelRequest{
  1510  				StoreId: "1",
  1511  				Id:      ulid.Make().String(),
  1512  			},
  1513  			output: output{
  1514  				errorCode: codes.InvalidArgument,
  1515  			},
  1516  		},
  1517  		{
  1518  			name: "invalid_storeID_because_extra_chars",
  1519  			input: &openfgav1.ReadAuthorizationModelRequest{
  1520  				StoreId: ulid.Make().String() + "A",
  1521  				Id:      ulid.Make().String(), // ulids aren't required at this time
  1522  			},
  1523  			output: output{
  1524  				errorCode: codes.InvalidArgument,
  1525  			},
  1526  		},
  1527  		{
  1528  			name: "invalid_authorization_model_ID_because_too_short",
  1529  			input: &openfgav1.ReadAuthorizationModelRequest{
  1530  				StoreId: ulid.Make().String(),
  1531  				Id:      "1",
  1532  			},
  1533  			output: output{
  1534  				errorCode: codes.InvalidArgument,
  1535  			},
  1536  		},
  1537  		{
  1538  			name: "invalid_authorization_model_ID_because_extra_chars",
  1539  			input: &openfgav1.ReadAuthorizationModelRequest{
  1540  				StoreId: ulid.Make().String(),
  1541  				Id:      ulid.Make().String() + "A",
  1542  			},
  1543  			output: output{
  1544  				errorCode: codes.InvalidArgument,
  1545  			},
  1546  		},
  1547  		{
  1548  			name: "missing_authorization_id",
  1549  			input: &openfgav1.ReadAuthorizationModelRequest{
  1550  				StoreId: ulid.Make().String(),
  1551  			},
  1552  			output: output{
  1553  				errorCode: codes.InvalidArgument,
  1554  			},
  1555  		},
  1556  	}
  1557  
  1558  	for _, test := range tests {
  1559  		t.Run(test.name, func(t *testing.T) {
  1560  			if test.testData != nil {
  1561  				modelResp, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{
  1562  					StoreId:         test.input.GetStoreId(),
  1563  					SchemaVersion:   typesystem.SchemaVersion1_1,
  1564  					TypeDefinitions: parser.MustTransformDSLToProto(test.testData.model).GetTypeDefinitions(),
  1565  				})
  1566  				test.input.Id = modelResp.GetAuthorizationModelId()
  1567  				require.NoError(t, err)
  1568  			}
  1569  			_, err := client.ReadAuthorizationModel(context.Background(), test.input)
  1570  
  1571  			s, ok := status.FromError(err)
  1572  			require.True(t, ok)
  1573  			require.Equal(t, test.output.errorCode.String(), s.Code().String())
  1574  
  1575  			if s.Code() == codes.OK {
  1576  				require.NoError(t, err)
  1577  			}
  1578  		})
  1579  	}
  1580  }
  1581  
  1582  func GRPCReadAuthorizationModelsTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
  1583  	storeID := ulid.Make().String()
  1584  
  1585  	_, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{
  1586  		StoreId: storeID,
  1587  		TypeDefinitions: []*openfgav1.TypeDefinition{
  1588  			{
  1589  				Type: "user",
  1590  			},
  1591  			{
  1592  				Type: "document",
  1593  				Relations: map[string]*openfgav1.Userset{
  1594  					"viewer": {Userset: &openfgav1.Userset_This{}},
  1595  				},
  1596  				Metadata: &openfgav1.Metadata{
  1597  					Relations: map[string]*openfgav1.RelationMetadata{
  1598  						"viewer": {
  1599  							DirectlyRelatedUserTypes: []*openfgav1.RelationReference{
  1600  								typesystem.DirectRelationReference("user", ""),
  1601  							},
  1602  						},
  1603  					},
  1604  				},
  1605  			},
  1606  		},
  1607  
  1608  		SchemaVersion: typesystem.SchemaVersion1_1,
  1609  	})
  1610  	require.NoError(t, err)
  1611  
  1612  	_, err = client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{
  1613  		StoreId: storeID,
  1614  		TypeDefinitions: []*openfgav1.TypeDefinition{
  1615  			{
  1616  				Type: "user",
  1617  			},
  1618  			{
  1619  				Type: "document",
  1620  				Relations: map[string]*openfgav1.Userset{
  1621  					"editor": {Userset: &openfgav1.Userset_This{}},
  1622  				},
  1623  				Metadata: &openfgav1.Metadata{
  1624  					Relations: map[string]*openfgav1.RelationMetadata{
  1625  						"editor": {
  1626  							DirectlyRelatedUserTypes: []*openfgav1.RelationReference{
  1627  								typesystem.DirectRelationReference("user", ""),
  1628  							},
  1629  						},
  1630  					},
  1631  				},
  1632  			},
  1633  		},
  1634  		SchemaVersion: typesystem.SchemaVersion1_1,
  1635  	})
  1636  	require.NoError(t, err)
  1637  
  1638  	resp1, err := client.ReadAuthorizationModels(context.Background(), &openfgav1.ReadAuthorizationModelsRequest{
  1639  		StoreId:  storeID,
  1640  		PageSize: wrapperspb.Int32(1),
  1641  	})
  1642  	require.NoError(t, err)
  1643  
  1644  	require.Len(t, resp1.GetAuthorizationModels(), 1)
  1645  	require.NotEmpty(t, resp1.GetContinuationToken())
  1646  
  1647  	resp2, err := client.ReadAuthorizationModels(context.Background(), &openfgav1.ReadAuthorizationModelsRequest{
  1648  		StoreId:           storeID,
  1649  		ContinuationToken: resp1.GetContinuationToken(),
  1650  	})
  1651  	require.NoError(t, err)
  1652  
  1653  	require.Len(t, resp2.GetAuthorizationModels(), 1)
  1654  	require.Empty(t, resp2.GetContinuationToken())
  1655  }
  1656  
  1657  func GRPCWriteAuthorizationModelTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
  1658  	type output struct {
  1659  		errorCode codes.Code
  1660  	}
  1661  
  1662  	tests := []struct {
  1663  		name   string
  1664  		input  *openfgav1.WriteAuthorizationModelRequest
  1665  		output output
  1666  	}{
  1667  		{
  1668  			name:  "empty_request",
  1669  			input: &openfgav1.WriteAuthorizationModelRequest{},
  1670  			output: output{
  1671  				errorCode: codes.InvalidArgument,
  1672  			},
  1673  		},
  1674  		{
  1675  			name: "invalid_storeID_because_too_short",
  1676  			input: &openfgav1.WriteAuthorizationModelRequest{
  1677  				StoreId: "1",
  1678  			},
  1679  			output: output{
  1680  				errorCode: codes.InvalidArgument,
  1681  			},
  1682  		},
  1683  		{
  1684  			name: "invalid_storeID_because_extra_chars",
  1685  			input: &openfgav1.WriteAuthorizationModelRequest{
  1686  				StoreId: ulid.Make().String() + "A",
  1687  			},
  1688  			output: output{
  1689  				errorCode: codes.InvalidArgument,
  1690  			},
  1691  		},
  1692  		{
  1693  			name: "missing_type_definitions",
  1694  			input: &openfgav1.WriteAuthorizationModelRequest{
  1695  				StoreId: ulid.Make().String(),
  1696  			},
  1697  			output: output{
  1698  				errorCode: codes.InvalidArgument,
  1699  			},
  1700  		},
  1701  		{
  1702  			name: "zero_type_definitions",
  1703  			input: &openfgav1.WriteAuthorizationModelRequest{
  1704  				StoreId:         ulid.Make().String(),
  1705  				TypeDefinitions: []*openfgav1.TypeDefinition{},
  1706  			},
  1707  			output: output{
  1708  				errorCode: codes.InvalidArgument,
  1709  			},
  1710  		},
  1711  		{
  1712  			name: "invalid_type_definition_because_empty_type_name",
  1713  			input: &openfgav1.WriteAuthorizationModelRequest{
  1714  				StoreId: ulid.Make().String(),
  1715  				TypeDefinitions: []*openfgav1.TypeDefinition{
  1716  					{
  1717  						Type: "",
  1718  						Relations: map[string]*openfgav1.Userset{
  1719  							"viewer": {Userset: &openfgav1.Userset_This{}},
  1720  						},
  1721  					},
  1722  				},
  1723  			},
  1724  			output: output{
  1725  				errorCode: codes.InvalidArgument,
  1726  			},
  1727  		},
  1728  		{
  1729  			name: "invalid_type_definition_because_too_many_chars_in_name",
  1730  			input: &openfgav1.WriteAuthorizationModelRequest{
  1731  				StoreId: ulid.Make().String(),
  1732  				TypeDefinitions: []*openfgav1.TypeDefinition{
  1733  					{
  1734  						Type: testutils.CreateRandomString(255),
  1735  						Relations: map[string]*openfgav1.Userset{
  1736  							"viewer": {Userset: &openfgav1.Userset_This{}},
  1737  						},
  1738  					},
  1739  				},
  1740  			},
  1741  			output: output{
  1742  				errorCode: codes.InvalidArgument,
  1743  			},
  1744  		},
  1745  		{
  1746  			name: "invalid_type_definition_because_invalid_chars_in_name",
  1747  			input: &openfgav1.WriteAuthorizationModelRequest{
  1748  				StoreId: ulid.Make().String(),
  1749  				TypeDefinitions: []*openfgav1.TypeDefinition{
  1750  					{
  1751  						Type: "some type",
  1752  						Relations: map[string]*openfgav1.Userset{
  1753  							"viewer": {Userset: &openfgav1.Userset_This{}},
  1754  						},
  1755  					},
  1756  				},
  1757  			},
  1758  			output: output{
  1759  				errorCode: codes.InvalidArgument,
  1760  			},
  1761  		},
  1762  	}
  1763  
  1764  	for _, test := range tests {
  1765  		t.Run(test.name, func(t *testing.T) {
  1766  			response, err := client.WriteAuthorizationModel(context.Background(), test.input)
  1767  
  1768  			s, ok := status.FromError(err)
  1769  			require.True(t, ok)
  1770  			require.Equal(t, test.output.errorCode.String(), s.Code().String())
  1771  
  1772  			if test.output.errorCode == codes.OK {
  1773  				_, err = ulid.Parse(response.GetAuthorizationModelId())
  1774  				require.NoError(t, err)
  1775  			} else {
  1776  				require.Error(t, err)
  1777  			}
  1778  		})
  1779  	}
  1780  }
  1781  
  1782  func GRPCWriteAssertionsTest(t *testing.T, client openfgav1.OpenFGAServiceClient) {
  1783  	type testData struct {
  1784  		model string
  1785  	}
  1786  	type output struct {
  1787  		statusCode   codes.Code
  1788  		errorMessage string
  1789  	}
  1790  
  1791  	tests := []struct {
  1792  		name     string
  1793  		input    *openfgav1.WriteAssertionsRequest
  1794  		testData *testData
  1795  		output   output
  1796  	}{
  1797  		{
  1798  			name: "happy_path",
  1799  			testData: &testData{
  1800  				model: `model
  1801  	schema 1.1
  1802  type user
  1803  
  1804  type document
  1805    relations
  1806  	define viewer: [user]`,
  1807  			},
  1808  			input: &openfgav1.WriteAssertionsRequest{
  1809  				StoreId:              ulid.Make().String(),
  1810  				AuthorizationModelId: ulid.Make().String(),
  1811  				Assertions: []*openfgav1.Assertion{
  1812  					{Expectation: true, TupleKey: &openfgav1.AssertionTupleKey{
  1813  						Object:   "document:1",
  1814  						Relation: "viewer",
  1815  						User:     "user:anne",
  1816  					}},
  1817  				},
  1818  			},
  1819  			output: output{
  1820  				statusCode: codes.OK,
  1821  			},
  1822  		},
  1823  		{
  1824  			name:  "empty_request",
  1825  			input: &openfgav1.WriteAssertionsRequest{},
  1826  			output: output{
  1827  				statusCode: codes.InvalidArgument,
  1828  			},
  1829  		},
  1830  		{
  1831  			name: "invalid_storeID_because_too_short",
  1832  			input: &openfgav1.WriteAssertionsRequest{
  1833  				StoreId:              "1",
  1834  				AuthorizationModelId: ulid.Make().String(),
  1835  			},
  1836  			output: output{
  1837  				statusCode: codes.InvalidArgument,
  1838  			},
  1839  		},
  1840  		{
  1841  			name: "invalid_storeID_because_extra_chars",
  1842  			input: &openfgav1.WriteAssertionsRequest{
  1843  				StoreId:              ulid.Make().String() + "A",
  1844  				AuthorizationModelId: ulid.Make().String(),
  1845  			},
  1846  			output: output{
  1847  				statusCode: codes.InvalidArgument,
  1848  			},
  1849  		},
  1850  		{
  1851  			name: "invalid_storeID_because_invalid_chars",
  1852  			input: &openfgav1.WriteAssertionsRequest{
  1853  				StoreId:              "ABCDEFGHIJKLMNOPQRSTUVWXY@",
  1854  				AuthorizationModelId: ulid.Make().String(),
  1855  			},
  1856  			output: output{
  1857  				statusCode: codes.InvalidArgument,
  1858  			},
  1859  		},
  1860  		{
  1861  			name: "invalid_authorization_model_ID_because_extra_chars",
  1862  			input: &openfgav1.WriteAssertionsRequest{
  1863  				StoreId:              ulid.Make().String(),
  1864  				AuthorizationModelId: ulid.Make().String() + "A",
  1865  			},
  1866  			output: output{
  1867  				statusCode: codes.InvalidArgument,
  1868  			},
  1869  		},
  1870  		{
  1871  			name: "invalid_authorization_model_ID_because_invalid_chars",
  1872  			input: &openfgav1.WriteAssertionsRequest{
  1873  				StoreId:              ulid.Make().String(),
  1874  				AuthorizationModelId: "ABCDEFGHIJKLMNOPQRSTUVWXY@",
  1875  			},
  1876  			output: output{
  1877  				statusCode: codes.InvalidArgument,
  1878  			},
  1879  		},
  1880  		{
  1881  			name: "missing_user_in_assertion",
  1882  			input: &openfgav1.WriteAssertionsRequest{
  1883  				StoreId:              ulid.Make().String(),
  1884  				AuthorizationModelId: ulid.Make().String(),
  1885  				Assertions: []*openfgav1.Assertion{
  1886  					{Expectation: true, TupleKey: &openfgav1.AssertionTupleKey{
  1887  						Object:   "obj:1",
  1888  						Relation: "viewer",
  1889  					}},
  1890  				},
  1891  			},
  1892  			output: output{
  1893  				statusCode: codes.InvalidArgument,
  1894  			},
  1895  		},
  1896  		{
  1897  			name: "missing_relation_in_assertion",
  1898  			input: &openfgav1.WriteAssertionsRequest{
  1899  				StoreId:              ulid.Make().String(),
  1900  				AuthorizationModelId: ulid.Make().String(),
  1901  				Assertions: []*openfgav1.Assertion{
  1902  					{Expectation: true, TupleKey: &openfgav1.AssertionTupleKey{
  1903  						Object: "obj:1",
  1904  						User:   "user:anne",
  1905  					}},
  1906  				},
  1907  			},
  1908  			output: output{
  1909  				statusCode: codes.InvalidArgument,
  1910  			},
  1911  		},
  1912  		{
  1913  			name: "missing_object_in_assertion",
  1914  			input: &openfgav1.WriteAssertionsRequest{
  1915  				StoreId:              ulid.Make().String(),
  1916  				AuthorizationModelId: ulid.Make().String(),
  1917  				Assertions: []*openfgav1.Assertion{
  1918  					{Expectation: true, TupleKey: &openfgav1.AssertionTupleKey{
  1919  						Relation: "viewer",
  1920  						User:     "user:anne",
  1921  					}},
  1922  				},
  1923  			},
  1924  			output: output{
  1925  				statusCode: codes.InvalidArgument,
  1926  			},
  1927  		},
  1928  		{
  1929  			name: "model_not_found",
  1930  			input: &openfgav1.WriteAssertionsRequest{
  1931  				StoreId:              ulid.Make().String(),
  1932  				AuthorizationModelId: ulid.Make().String(),
  1933  				Assertions: []*openfgav1.Assertion{
  1934  					{Expectation: true, TupleKey: &openfgav1.AssertionTupleKey{
  1935  						Object:   "obj:1",
  1936  						Relation: "viewer",
  1937  						User:     "user:anne",
  1938  					}},
  1939  				},
  1940  			},
  1941  			output: output{
  1942  				statusCode: 2001, // ErrorCode_authorization_model_not_found
  1943  			},
  1944  		},
  1945  	}
  1946  
  1947  	for _, test := range tests {
  1948  		t.Run(test.name, func(t *testing.T) {
  1949  			if test.testData != nil {
  1950  				modelResp, err := client.WriteAuthorizationModel(context.Background(), &openfgav1.WriteAuthorizationModelRequest{
  1951  					StoreId:         test.input.GetStoreId(),
  1952  					SchemaVersion:   typesystem.SchemaVersion1_1,
  1953  					TypeDefinitions: parser.MustTransformDSLToProto(test.testData.model).GetTypeDefinitions(),
  1954  				})
  1955  				test.input.AuthorizationModelId = modelResp.GetAuthorizationModelId()
  1956  				require.NoError(t, err)
  1957  			}
  1958  			_, err := client.WriteAssertions(context.Background(), test.input)
  1959  
  1960  			s, ok := status.FromError(err)
  1961  
  1962  			require.True(t, ok)
  1963  			require.Equal(t, test.output.statusCode.String(), s.Code().String())
  1964  
  1965  			if s.Code() == codes.OK {
  1966  				require.NoError(t, err)
  1967  			} else {
  1968  				require.Error(t, err)
  1969  				require.Contains(t, err.Error(), test.output.errorMessage)
  1970  			}
  1971  		})
  1972  	}
  1973  }