github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/services/v1/hash_test.go (about)

     1  package v1
     2  
     3  import (
     4  	"testing"
     5  
     6  	"google.golang.org/protobuf/types/known/structpb"
     7  
     8  	v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
     9  	"github.com/stretchr/testify/require"
    10  )
    11  
    12  func TestReadRelationshipsHashStability(t *testing.T) {
    13  	tcs := []struct {
    14  		name         string
    15  		request      *v1.ReadRelationshipsRequest
    16  		expectedHash string
    17  	}{
    18  		{
    19  			"basic read",
    20  			&v1.ReadRelationshipsRequest{
    21  				RelationshipFilter: &v1.RelationshipFilter{
    22  					ResourceType: "someresourcetype",
    23  				},
    24  			},
    25  			"0a0e721527f9e3b2",
    26  		},
    27  		{
    28  			"full read",
    29  			&v1.ReadRelationshipsRequest{
    30  				RelationshipFilter: &v1.RelationshipFilter{
    31  					ResourceType:       "someresourcetype",
    32  					OptionalResourceId: "foo",
    33  					OptionalRelation:   "somerelation",
    34  					OptionalSubjectFilter: &v1.SubjectFilter{
    35  						SubjectType:       "somesubjectype",
    36  						OptionalSubjectId: "somesubject",
    37  						OptionalRelation: &v1.SubjectFilter_RelationFilter{
    38  							Relation: "subrelation",
    39  						},
    40  					},
    41  				},
    42  				OptionalLimit: 1000,
    43  			},
    44  			"0375e86e0c72f281",
    45  		},
    46  		{
    47  			"different resource type",
    48  			&v1.ReadRelationshipsRequest{
    49  				RelationshipFilter: &v1.RelationshipFilter{
    50  					ResourceType:       "someresourcetype2",
    51  					OptionalResourceId: "foo",
    52  					OptionalRelation:   "somerelation",
    53  					OptionalSubjectFilter: &v1.SubjectFilter{
    54  						SubjectType:       "somesubjectype",
    55  						OptionalSubjectId: "somesubject",
    56  						OptionalRelation: &v1.SubjectFilter_RelationFilter{
    57  							Relation: "subrelation",
    58  						},
    59  					},
    60  				},
    61  				OptionalLimit: 1000,
    62  			},
    63  			"a7d3548f8303f0ba",
    64  		},
    65  		{
    66  			"different resource id",
    67  			&v1.ReadRelationshipsRequest{
    68  				RelationshipFilter: &v1.RelationshipFilter{
    69  					ResourceType:       "someresourcetype",
    70  					OptionalResourceId: "foo2",
    71  					OptionalRelation:   "somerelation",
    72  					OptionalSubjectFilter: &v1.SubjectFilter{
    73  						SubjectType:       "somesubjectype",
    74  						OptionalSubjectId: "somesubject",
    75  						OptionalRelation: &v1.SubjectFilter_RelationFilter{
    76  							Relation: "subrelation",
    77  						},
    78  					},
    79  				},
    80  				OptionalLimit: 1000,
    81  			},
    82  			"77c9a81be9232973",
    83  		},
    84  		{
    85  			"different resource relation",
    86  			&v1.ReadRelationshipsRequest{
    87  				RelationshipFilter: &v1.RelationshipFilter{
    88  					ResourceType:       "someresourcetype",
    89  					OptionalResourceId: "foo",
    90  					OptionalRelation:   "somerelation2",
    91  					OptionalSubjectFilter: &v1.SubjectFilter{
    92  						SubjectType:       "somesubjectype",
    93  						OptionalSubjectId: "somesubject",
    94  						OptionalRelation: &v1.SubjectFilter_RelationFilter{
    95  							Relation: "subrelation",
    96  						},
    97  					},
    98  				},
    99  				OptionalLimit: 1000,
   100  			},
   101  			"765b640f81f98ff5",
   102  		},
   103  		{
   104  			"no subject filter",
   105  			&v1.ReadRelationshipsRequest{
   106  				RelationshipFilter: &v1.RelationshipFilter{
   107  					ResourceType:       "someresourcetype",
   108  					OptionalResourceId: "foo",
   109  					OptionalRelation:   "somerelation",
   110  				},
   111  				OptionalLimit: 1000,
   112  			},
   113  			"42abbaaaf9d6cb12",
   114  		},
   115  		{
   116  			"different subject type",
   117  			&v1.ReadRelationshipsRequest{
   118  				RelationshipFilter: &v1.RelationshipFilter{
   119  					ResourceType:       "someresourcetype",
   120  					OptionalResourceId: "foo",
   121  					OptionalRelation:   "somerelation",
   122  					OptionalSubjectFilter: &v1.SubjectFilter{
   123  						SubjectType:       "somesubjectype2",
   124  						OptionalSubjectId: "somesubject",
   125  						OptionalRelation: &v1.SubjectFilter_RelationFilter{
   126  							Relation: "subrelation",
   127  						},
   128  					},
   129  				},
   130  				OptionalLimit: 1000,
   131  			},
   132  			"55b37fccd04ae2df",
   133  		},
   134  		{
   135  			"different subject id",
   136  			&v1.ReadRelationshipsRequest{
   137  				RelationshipFilter: &v1.RelationshipFilter{
   138  					ResourceType:       "someresourcetype",
   139  					OptionalResourceId: "foo",
   140  					OptionalRelation:   "somerelation",
   141  					OptionalSubjectFilter: &v1.SubjectFilter{
   142  						SubjectType:       "somesubjectype",
   143  						OptionalSubjectId: "somesubject2",
   144  						OptionalRelation: &v1.SubjectFilter_RelationFilter{
   145  							Relation: "subrelation",
   146  						},
   147  					},
   148  				},
   149  				OptionalLimit: 1000,
   150  			},
   151  			"0ba2e49c26fb8ae1",
   152  		},
   153  		{
   154  			"different subject relation",
   155  			&v1.ReadRelationshipsRequest{
   156  				RelationshipFilter: &v1.RelationshipFilter{
   157  					ResourceType:       "someresourcetype",
   158  					OptionalResourceId: "foo",
   159  					OptionalRelation:   "somerelation",
   160  					OptionalSubjectFilter: &v1.SubjectFilter{
   161  						SubjectType:       "somesubjectype",
   162  						OptionalSubjectId: "somesubject",
   163  						OptionalRelation: &v1.SubjectFilter_RelationFilter{
   164  							Relation: "subrelation2",
   165  						},
   166  					},
   167  				},
   168  				OptionalLimit: 1000,
   169  			},
   170  			"73e715ccd9ea2225",
   171  		},
   172  		{
   173  			"different limit",
   174  			&v1.ReadRelationshipsRequest{
   175  				RelationshipFilter: &v1.RelationshipFilter{
   176  					ResourceType:       "someresourcetype",
   177  					OptionalResourceId: "foo",
   178  					OptionalRelation:   "somerelation",
   179  					OptionalSubjectFilter: &v1.SubjectFilter{
   180  						SubjectType:       "somesubjectype",
   181  						OptionalSubjectId: "somesubject",
   182  						OptionalRelation: &v1.SubjectFilter_RelationFilter{
   183  							Relation: "subrelation",
   184  						},
   185  					},
   186  				},
   187  				OptionalLimit: 999,
   188  			},
   189  			"7e7fdc450bf08327",
   190  		},
   191  	}
   192  
   193  	for _, tc := range tcs {
   194  		tc := tc
   195  		t.Run(tc.name, func(t *testing.T) {
   196  			verr := tc.request.Validate()
   197  			require.NoError(t, verr)
   198  
   199  			hash, err := computeReadRelationshipsRequestHash(tc.request)
   200  			require.NoError(t, err)
   201  			require.Equal(t, tc.expectedHash, hash)
   202  		})
   203  	}
   204  }
   205  
   206  func TestLRHashStability(t *testing.T) {
   207  	tcs := []struct {
   208  		name         string
   209  		request      *v1.LookupResourcesRequest
   210  		expectedHash string
   211  	}{
   212  		{
   213  			"basic LR",
   214  			&v1.LookupResourcesRequest{
   215  				ResourceObjectType: "resource",
   216  				Permission:         "view",
   217  				Subject: &v1.SubjectReference{
   218  					Object: &v1.ObjectReference{
   219  						ObjectType: "user",
   220  						ObjectId:   "tom",
   221  					},
   222  				},
   223  				Consistency: &v1.Consistency{
   224  					Requirement: &v1.Consistency_MinimizeLatency{
   225  						MinimizeLatency: true,
   226  					},
   227  				},
   228  				OptionalLimit: 1000,
   229  			},
   230  			"f5c7ca6296253717",
   231  		},
   232  		{
   233  			"different LR subject",
   234  			&v1.LookupResourcesRequest{
   235  				ResourceObjectType: "resource",
   236  				Permission:         "view",
   237  				Subject: &v1.SubjectReference{
   238  					Object: &v1.ObjectReference{
   239  						ObjectType: "user",
   240  						ObjectId:   "sarah",
   241  					},
   242  				},
   243  				Consistency: &v1.Consistency{
   244  					Requirement: &v1.Consistency_MinimizeLatency{
   245  						MinimizeLatency: true,
   246  					},
   247  				},
   248  				OptionalLimit: 1000,
   249  			},
   250  			"aa8b67b886ecf3fd",
   251  		},
   252  		{
   253  			"different LR resource",
   254  			&v1.LookupResourcesRequest{
   255  				ResourceObjectType: "resource2",
   256  				Permission:         "view",
   257  				Subject: &v1.SubjectReference{
   258  					Object: &v1.ObjectReference{
   259  						ObjectType: "user",
   260  						ObjectId:   "tom",
   261  					},
   262  				},
   263  				Consistency: &v1.Consistency{
   264  					Requirement: &v1.Consistency_MinimizeLatency{
   265  						MinimizeLatency: true,
   266  					},
   267  				},
   268  				OptionalLimit: 1000,
   269  			},
   270  			"fb16e4dd9395864a",
   271  		},
   272  		{
   273  			"different LR resource permission",
   274  			&v1.LookupResourcesRequest{
   275  				ResourceObjectType: "resource",
   276  				Permission:         "viewer",
   277  				Subject: &v1.SubjectReference{
   278  					Object: &v1.ObjectReference{
   279  						ObjectType: "user",
   280  						ObjectId:   "tom",
   281  					},
   282  				},
   283  				Consistency: &v1.Consistency{
   284  					Requirement: &v1.Consistency_MinimizeLatency{
   285  						MinimizeLatency: true,
   286  					},
   287  				},
   288  				OptionalLimit: 1000,
   289  			},
   290  			"593e60bf77f8bdb4",
   291  		},
   292  		{
   293  			"different limit LR",
   294  			&v1.LookupResourcesRequest{
   295  				ResourceObjectType: "resource",
   296  				Permission:         "view",
   297  				Subject: &v1.SubjectReference{
   298  					Object: &v1.ObjectReference{
   299  						ObjectType: "user",
   300  						ObjectId:   "sarah",
   301  					},
   302  				},
   303  				Consistency: &v1.Consistency{
   304  					Requirement: &v1.Consistency_MinimizeLatency{
   305  						MinimizeLatency: true,
   306  					},
   307  				},
   308  				OptionalLimit: 999,
   309  			},
   310  			"0097cf2ee303ec31",
   311  		},
   312  		{
   313  			"LR with different consistency",
   314  			&v1.LookupResourcesRequest{
   315  				ResourceObjectType: "resource",
   316  				Permission:         "view",
   317  				Subject: &v1.SubjectReference{
   318  					Object: &v1.ObjectReference{
   319  						ObjectType: "user",
   320  						ObjectId:   "tom",
   321  					},
   322  				},
   323  				Consistency: &v1.Consistency{
   324  					Requirement: &v1.Consistency_FullyConsistent{
   325  						FullyConsistent: true,
   326  					},
   327  				},
   328  				OptionalLimit: 1000,
   329  			},
   330  			"d8b707db35cb7043",
   331  		},
   332  		{
   333  			"basic LR with caveat context",
   334  			&v1.LookupResourcesRequest{
   335  				ResourceObjectType: "resource",
   336  				Permission:         "view",
   337  				Subject: &v1.SubjectReference{
   338  					Object: &v1.ObjectReference{
   339  						ObjectType: "user",
   340  						ObjectId:   "tom",
   341  					},
   342  				},
   343  				Consistency: &v1.Consistency{
   344  					Requirement: &v1.Consistency_MinimizeLatency{
   345  						MinimizeLatency: true,
   346  					},
   347  				},
   348  				OptionalLimit: 1000,
   349  				Context: func() *structpb.Struct {
   350  					s, _ := structpb.NewStruct(map[string]any{
   351  						"somecondition":    42,
   352  						"anothercondition": "hello world",
   353  					})
   354  					return s
   355  				}(),
   356  			},
   357  			"d40193c84ec59e6f",
   358  		},
   359  		{
   360  			"basic LR with different caveat context",
   361  			&v1.LookupResourcesRequest{
   362  				ResourceObjectType: "resource",
   363  				Permission:         "view",
   364  				Subject: &v1.SubjectReference{
   365  					Object: &v1.ObjectReference{
   366  						ObjectType: "user",
   367  						ObjectId:   "tom",
   368  					},
   369  				},
   370  				Consistency: &v1.Consistency{
   371  					Requirement: &v1.Consistency_MinimizeLatency{
   372  						MinimizeLatency: true,
   373  					},
   374  				},
   375  				OptionalLimit: 1000,
   376  				Context: func() *structpb.Struct {
   377  					s, _ := structpb.NewStruct(map[string]any{
   378  						"somecondition":    43,
   379  						"anothercondition": "hello world",
   380  					})
   381  					return s
   382  				}(),
   383  			},
   384  			"a5af756163998c88",
   385  		},
   386  	}
   387  
   388  	for _, tc := range tcs {
   389  		tc := tc
   390  		t.Run(tc.name, func(t *testing.T) {
   391  			verr := tc.request.Validate()
   392  			require.NoError(t, verr)
   393  
   394  			hash, err := computeLRRequestHash(tc.request)
   395  			require.NoError(t, err)
   396  			require.Equal(t, tc.expectedHash, hash)
   397  		})
   398  	}
   399  }
   400  
   401  func TestCheckBulkPermissionsItemWithoutResourceIDHashStability(t *testing.T) {
   402  	tcs := []struct {
   403  		name         string
   404  		request      *v1.CheckBulkPermissionsRequestItem
   405  		expectedHash string
   406  	}{
   407  		{
   408  			"basic bulk check item",
   409  			&v1.CheckBulkPermissionsRequestItem{
   410  				Resource: &v1.ObjectReference{
   411  					ObjectType: "resource",
   412  					ObjectId:   "someid",
   413  				},
   414  				Permission: "view",
   415  				Subject: &v1.SubjectReference{
   416  					Object: &v1.ObjectReference{
   417  						ObjectType: "user",
   418  						ObjectId:   "tom",
   419  					},
   420  				},
   421  			},
   422  			"f518629690bd9dc0",
   423  		},
   424  		{
   425  			"different resource ID, should still be the same hash",
   426  			&v1.CheckBulkPermissionsRequestItem{
   427  				Resource: &v1.ObjectReference{
   428  					ObjectType: "resource",
   429  					ObjectId:   "someid2",
   430  				},
   431  				Permission: "view",
   432  				Subject: &v1.SubjectReference{
   433  					Object: &v1.ObjectReference{
   434  						ObjectType: "user",
   435  						ObjectId:   "tom",
   436  					},
   437  				},
   438  			},
   439  			"f518629690bd9dc0",
   440  		},
   441  		{
   442  			"basic bulk check item - transcribed letter",
   443  			&v1.CheckBulkPermissionsRequestItem{
   444  				Resource: &v1.ObjectReference{
   445  					ObjectType: "resourc",
   446  					ObjectId:   "esomeid",
   447  				},
   448  				Permission: "view",
   449  				Subject: &v1.SubjectReference{
   450  					Object: &v1.ObjectReference{
   451  						ObjectType: "user",
   452  						ObjectId:   "tom",
   453  					},
   454  				},
   455  			},
   456  			"60f1e177297e915e",
   457  		},
   458  		{
   459  			"different resource type",
   460  			&v1.CheckBulkPermissionsRequestItem{
   461  				Resource: &v1.ObjectReference{
   462  					ObjectType: "resource2",
   463  					ObjectId:   "someid",
   464  				},
   465  				Permission: "view",
   466  				Subject: &v1.SubjectReference{
   467  					Object: &v1.ObjectReference{
   468  						ObjectType: "user",
   469  						ObjectId:   "tom",
   470  					},
   471  				},
   472  			},
   473  			"5117abaee3adf638",
   474  		},
   475  		{
   476  			"different permission",
   477  			&v1.CheckBulkPermissionsRequestItem{
   478  				Resource: &v1.ObjectReference{
   479  					ObjectType: "resource",
   480  					ObjectId:   "someid",
   481  				},
   482  				Permission: "view2",
   483  				Subject: &v1.SubjectReference{
   484  					Object: &v1.ObjectReference{
   485  						ObjectType: "user",
   486  						ObjectId:   "tom",
   487  					},
   488  				},
   489  			},
   490  			"716f7be27e600292",
   491  		},
   492  		{
   493  			"different subject type",
   494  			&v1.CheckBulkPermissionsRequestItem{
   495  				Resource: &v1.ObjectReference{
   496  					ObjectType: "resource",
   497  					ObjectId:   "someid",
   498  				},
   499  				Permission: "view",
   500  				Subject: &v1.SubjectReference{
   501  					Object: &v1.ObjectReference{
   502  						ObjectType: "user2",
   503  						ObjectId:   "tom",
   504  					},
   505  				},
   506  			},
   507  			"7cb5945314ccbdce",
   508  		},
   509  		{
   510  			"different subject id",
   511  			&v1.CheckBulkPermissionsRequestItem{
   512  				Resource: &v1.ObjectReference{
   513  					ObjectType: "resource",
   514  					ObjectId:   "someid",
   515  				},
   516  				Permission: "view",
   517  				Subject: &v1.SubjectReference{
   518  					Object: &v1.ObjectReference{
   519  						ObjectType: "user",
   520  						ObjectId:   "tom2",
   521  					},
   522  				},
   523  			},
   524  			"b24ecacf87fd0bb8",
   525  		},
   526  		{
   527  			"different subject relation",
   528  			&v1.CheckBulkPermissionsRequestItem{
   529  				Resource: &v1.ObjectReference{
   530  					ObjectType: "resource",
   531  					ObjectId:   "someid",
   532  				},
   533  				Permission: "view",
   534  				Subject: &v1.SubjectReference{
   535  					Object: &v1.ObjectReference{
   536  						ObjectType: "user",
   537  						ObjectId:   "tom",
   538  					},
   539  					OptionalRelation: "foo",
   540  				},
   541  			},
   542  			"ee8c34ab206c80d7",
   543  		},
   544  		{
   545  			"with context",
   546  			&v1.CheckBulkPermissionsRequestItem{
   547  				Resource: &v1.ObjectReference{
   548  					ObjectType: "resource",
   549  					ObjectId:   "someid",
   550  				},
   551  				Permission: "view",
   552  				Subject: &v1.SubjectReference{
   553  					Object: &v1.ObjectReference{
   554  						ObjectType: "user",
   555  						ObjectId:   "tom",
   556  					},
   557  					OptionalRelation: "foo",
   558  				},
   559  				Context: func() *structpb.Struct {
   560  					s, _ := structpb.NewStruct(map[string]any{
   561  						"somecondition":    42,
   562  						"anothercondition": "hello world",
   563  					})
   564  					return s
   565  				}(),
   566  			},
   567  			"7a5b1fec3cbed446",
   568  		},
   569  		{
   570  			"with different context",
   571  			&v1.CheckBulkPermissionsRequestItem{
   572  				Resource: &v1.ObjectReference{
   573  					ObjectType: "resource",
   574  					ObjectId:   "someid",
   575  				},
   576  				Permission: "view",
   577  				Subject: &v1.SubjectReference{
   578  					Object: &v1.ObjectReference{
   579  						ObjectType: "user",
   580  						ObjectId:   "tom",
   581  					},
   582  					OptionalRelation: "foo",
   583  				},
   584  				Context: func() *structpb.Struct {
   585  					s, _ := structpb.NewStruct(map[string]any{
   586  						"somecondition":    42,
   587  						"anothercondition": "hi there",
   588  					})
   589  					return s
   590  				}(),
   591  			},
   592  			"f17da513a6207c30",
   593  		},
   594  	}
   595  
   596  	for _, tc := range tcs {
   597  		tc := tc
   598  		t.Run(tc.name, func(t *testing.T) {
   599  			verr := tc.request.Validate()
   600  			require.NoError(t, verr)
   601  
   602  			hash, err := computeCheckBulkPermissionsItemHashWithoutResourceID(tc.request)
   603  			require.NoError(t, err)
   604  			require.Equal(t, tc.expectedHash, hash)
   605  		})
   606  	}
   607  }
   608  
   609  func TestCheckBulkPermissionsItemWIDHashStability(t *testing.T) {
   610  	tcs := []struct {
   611  		name         string
   612  		request      *v1.CheckBulkPermissionsRequestItem
   613  		expectedHash string
   614  	}{
   615  		{
   616  			"basic bulk check item",
   617  			&v1.CheckBulkPermissionsRequestItem{
   618  				Resource: &v1.ObjectReference{
   619  					ObjectType: "resource",
   620  					ObjectId:   "someid",
   621  				},
   622  				Permission: "view",
   623  				Subject: &v1.SubjectReference{
   624  					Object: &v1.ObjectReference{
   625  						ObjectType: "user",
   626  						ObjectId:   "tom",
   627  					},
   628  				},
   629  			},
   630  			"5edbb3bbb8079754",
   631  		},
   632  		{
   633  			"different resource ID, should be a different hash",
   634  			&v1.CheckBulkPermissionsRequestItem{
   635  				Resource: &v1.ObjectReference{
   636  					ObjectType: "resource",
   637  					ObjectId:   "someid2",
   638  				},
   639  				Permission: "view",
   640  				Subject: &v1.SubjectReference{
   641  					Object: &v1.ObjectReference{
   642  						ObjectType: "user",
   643  						ObjectId:   "tom",
   644  					},
   645  				},
   646  			},
   647  			"e6711064500e65ba",
   648  		},
   649  		{
   650  			"basic bulk check item - transcribed letter",
   651  			&v1.CheckBulkPermissionsRequestItem{
   652  				Resource: &v1.ObjectReference{
   653  					ObjectType: "resourc",
   654  					ObjectId:   "esomeid",
   655  				},
   656  				Permission: "view",
   657  				Subject: &v1.SubjectReference{
   658  					Object: &v1.ObjectReference{
   659  						ObjectType: "user",
   660  						ObjectId:   "tom",
   661  					},
   662  				},
   663  			},
   664  			"8cda00b7188572b7",
   665  		},
   666  		{
   667  			"different resource type",
   668  			&v1.CheckBulkPermissionsRequestItem{
   669  				Resource: &v1.ObjectReference{
   670  					ObjectType: "resource2",
   671  					ObjectId:   "someid",
   672  				},
   673  				Permission: "view",
   674  				Subject: &v1.SubjectReference{
   675  					Object: &v1.ObjectReference{
   676  						ObjectType: "user",
   677  						ObjectId:   "tom",
   678  					},
   679  				},
   680  			},
   681  			"51df43a69e51d3b0",
   682  		},
   683  		{
   684  			"different permission",
   685  			&v1.CheckBulkPermissionsRequestItem{
   686  				Resource: &v1.ObjectReference{
   687  					ObjectType: "resource",
   688  					ObjectId:   "someid",
   689  				},
   690  				Permission: "view2",
   691  				Subject: &v1.SubjectReference{
   692  					Object: &v1.ObjectReference{
   693  						ObjectType: "user",
   694  						ObjectId:   "tom",
   695  					},
   696  				},
   697  			},
   698  			"62aaa50b2821130d",
   699  		},
   700  		{
   701  			"different subject type",
   702  			&v1.CheckBulkPermissionsRequestItem{
   703  				Resource: &v1.ObjectReference{
   704  					ObjectType: "resource",
   705  					ObjectId:   "someid",
   706  				},
   707  				Permission: "view",
   708  				Subject: &v1.SubjectReference{
   709  					Object: &v1.ObjectReference{
   710  						ObjectType: "user2",
   711  						ObjectId:   "tom",
   712  					},
   713  				},
   714  			},
   715  			"82a445d3ffc0823a",
   716  		},
   717  		{
   718  			"different subject id",
   719  			&v1.CheckBulkPermissionsRequestItem{
   720  				Resource: &v1.ObjectReference{
   721  					ObjectType: "resource",
   722  					ObjectId:   "someid",
   723  				},
   724  				Permission: "view",
   725  				Subject: &v1.SubjectReference{
   726  					Object: &v1.ObjectReference{
   727  						ObjectType: "user",
   728  						ObjectId:   "tom2",
   729  					},
   730  				},
   731  			},
   732  			"d3d624a310fa7781",
   733  		},
   734  		{
   735  			"different subject relation",
   736  			&v1.CheckBulkPermissionsRequestItem{
   737  				Resource: &v1.ObjectReference{
   738  					ObjectType: "resource",
   739  					ObjectId:   "someid",
   740  				},
   741  				Permission: "view",
   742  				Subject: &v1.SubjectReference{
   743  					Object: &v1.ObjectReference{
   744  						ObjectType: "user",
   745  						ObjectId:   "tom",
   746  					},
   747  					OptionalRelation: "foo",
   748  				},
   749  			},
   750  			"a9d96f0572caef89",
   751  		},
   752  		{
   753  			"with context",
   754  			&v1.CheckBulkPermissionsRequestItem{
   755  				Resource: &v1.ObjectReference{
   756  					ObjectType: "resource",
   757  					ObjectId:   "someid",
   758  				},
   759  				Permission: "view",
   760  				Subject: &v1.SubjectReference{
   761  					Object: &v1.ObjectReference{
   762  						ObjectType: "user",
   763  						ObjectId:   "tom",
   764  					},
   765  					OptionalRelation: "foo",
   766  				},
   767  				Context: func() *structpb.Struct {
   768  					s, _ := structpb.NewStruct(map[string]any{
   769  						"somecondition":    42,
   770  						"anothercondition": "hello world",
   771  					})
   772  					return s
   773  				}(),
   774  			},
   775  			"94dea3fccff039ed",
   776  		},
   777  		{
   778  			"with different context",
   779  			&v1.CheckBulkPermissionsRequestItem{
   780  				Resource: &v1.ObjectReference{
   781  					ObjectType: "resource",
   782  					ObjectId:   "someid",
   783  				},
   784  				Permission: "view",
   785  				Subject: &v1.SubjectReference{
   786  					Object: &v1.ObjectReference{
   787  						ObjectType: "user",
   788  						ObjectId:   "tom",
   789  					},
   790  					OptionalRelation: "foo",
   791  				},
   792  				Context: func() *structpb.Struct {
   793  					s, _ := structpb.NewStruct(map[string]any{
   794  						"somecondition":    42,
   795  						"anothercondition": "hi there",
   796  					})
   797  					return s
   798  				}(),
   799  			},
   800  			"7ffdedbe12d578ee",
   801  		},
   802  	}
   803  
   804  	for _, tc := range tcs {
   805  		tc := tc
   806  		t.Run(tc.name, func(t *testing.T) {
   807  			verr := tc.request.Validate()
   808  			require.NoError(t, verr)
   809  
   810  			hash, err := computeCheckBulkPermissionsItemHash(tc.request)
   811  			require.NoError(t, err)
   812  			require.Equal(t, tc.expectedHash, hash)
   813  		})
   814  	}
   815  }
   816  
   817  func TestLSHashStability(t *testing.T) {
   818  	tcs := []struct {
   819  		name         string
   820  		request      *v1.LookupSubjectsRequest
   821  		expectedHash string
   822  	}{
   823  		{
   824  			"basic LS",
   825  			&v1.LookupSubjectsRequest{
   826  				SubjectObjectType: "subject",
   827  				Permission:        "view",
   828  				Resource: &v1.ObjectReference{
   829  					ObjectType: "resource",
   830  					ObjectId:   "somedoc",
   831  				},
   832  				Consistency: &v1.Consistency{
   833  					Requirement: &v1.Consistency_MinimizeLatency{
   834  						MinimizeLatency: true,
   835  					},
   836  				},
   837  				OptionalConcreteLimit: 1000,
   838  			},
   839  			"15f87f570009e190",
   840  		},
   841  		{
   842  			"different subject",
   843  			&v1.LookupSubjectsRequest{
   844  				SubjectObjectType: "subject2",
   845  				Permission:        "view",
   846  				Resource: &v1.ObjectReference{
   847  					ObjectType: "resource",
   848  					ObjectId:   "somedoc",
   849  				},
   850  				Consistency: &v1.Consistency{
   851  					Requirement: &v1.Consistency_MinimizeLatency{
   852  						MinimizeLatency: true,
   853  					},
   854  				},
   855  				OptionalConcreteLimit: 1000,
   856  			},
   857  			"a41898256f42203a",
   858  		},
   859  		{
   860  			"different permission",
   861  			&v1.LookupSubjectsRequest{
   862  				SubjectObjectType: "subject",
   863  				Permission:        "view2",
   864  				Resource: &v1.ObjectReference{
   865  					ObjectType: "resource",
   866  					ObjectId:   "somedoc",
   867  				},
   868  				Consistency: &v1.Consistency{
   869  					Requirement: &v1.Consistency_MinimizeLatency{
   870  						MinimizeLatency: true,
   871  					},
   872  				},
   873  				OptionalConcreteLimit: 1000,
   874  			},
   875  			"5dbe04c00a1cd2b0",
   876  		},
   877  		{
   878  			"different resource type",
   879  			&v1.LookupSubjectsRequest{
   880  				SubjectObjectType: "subject",
   881  				Permission:        "view",
   882  				Resource: &v1.ObjectReference{
   883  					ObjectType: "resource2",
   884  					ObjectId:   "somedoc",
   885  				},
   886  				Consistency: &v1.Consistency{
   887  					Requirement: &v1.Consistency_MinimizeLatency{
   888  						MinimizeLatency: true,
   889  					},
   890  				},
   891  				OptionalConcreteLimit: 1000,
   892  			},
   893  			"0ede1ecdd53c204f",
   894  		},
   895  		{
   896  			"different resource id",
   897  			&v1.LookupSubjectsRequest{
   898  				SubjectObjectType: "subject",
   899  				Permission:        "view",
   900  				Resource: &v1.ObjectReference{
   901  					ObjectType: "resource",
   902  					ObjectId:   "somedoc2",
   903  				},
   904  				Consistency: &v1.Consistency{
   905  					Requirement: &v1.Consistency_MinimizeLatency{
   906  						MinimizeLatency: true,
   907  					},
   908  				},
   909  				OptionalConcreteLimit: 1000,
   910  			},
   911  			"5f957ee550300986",
   912  		},
   913  		{
   914  			"no limit",
   915  			&v1.LookupSubjectsRequest{
   916  				SubjectObjectType: "subject",
   917  				Permission:        "view",
   918  				Resource: &v1.ObjectReference{
   919  					ObjectType: "resource",
   920  					ObjectId:   "somedoc",
   921  				},
   922  				Consistency: &v1.Consistency{
   923  					Requirement: &v1.Consistency_MinimizeLatency{
   924  						MinimizeLatency: true,
   925  					},
   926  				},
   927  			},
   928  			"dc3f5673a6a3d173",
   929  		},
   930  		{
   931  			"different limit",
   932  			&v1.LookupSubjectsRequest{
   933  				SubjectObjectType: "subject",
   934  				Permission:        "view",
   935  				Resource: &v1.ObjectReference{
   936  					ObjectType: "resource",
   937  					ObjectId:   "somedoc",
   938  				},
   939  				Consistency: &v1.Consistency{
   940  					Requirement: &v1.Consistency_MinimizeLatency{
   941  						MinimizeLatency: true,
   942  					},
   943  				},
   944  				OptionalConcreteLimit: 999,
   945  			},
   946  			"3b350c4c36efb985",
   947  		},
   948  		{
   949  			"default wildcard option",
   950  			&v1.LookupSubjectsRequest{
   951  				SubjectObjectType: "subject",
   952  				Permission:        "view",
   953  				Resource: &v1.ObjectReference{
   954  					ObjectType: "resource",
   955  					ObjectId:   "somedoc",
   956  				},
   957  				Consistency: &v1.Consistency{
   958  					Requirement: &v1.Consistency_MinimizeLatency{
   959  						MinimizeLatency: true,
   960  					},
   961  				},
   962  				OptionalConcreteLimit: 1000,
   963  				WildcardOption:        v1.LookupSubjectsRequest_WILDCARD_OPTION_UNSPECIFIED,
   964  			},
   965  			"15f87f570009e190",
   966  		},
   967  		{
   968  			"different wildcard option",
   969  			&v1.LookupSubjectsRequest{
   970  				SubjectObjectType: "subject",
   971  				Permission:        "view",
   972  				Resource: &v1.ObjectReference{
   973  					ObjectType: "resource",
   974  					ObjectId:   "somedoc",
   975  				},
   976  				Consistency: &v1.Consistency{
   977  					Requirement: &v1.Consistency_MinimizeLatency{
   978  						MinimizeLatency: true,
   979  					},
   980  				},
   981  				OptionalConcreteLimit: 1000,
   982  				WildcardOption:        v1.LookupSubjectsRequest_WILDCARD_OPTION_EXCLUDE_WILDCARDS,
   983  			},
   984  			"df28dbb33cdcc8dd",
   985  		},
   986  	}
   987  
   988  	for _, tc := range tcs {
   989  		tc := tc
   990  		t.Run(tc.name, func(t *testing.T) {
   991  			verr := tc.request.Validate()
   992  			require.NoError(t, verr)
   993  
   994  			hash, err := computeLSRequestHash(tc.request)
   995  			require.NoError(t, err)
   996  			require.Equal(t, tc.expectedHash, hash)
   997  		})
   998  	}
   999  }