github.com/openfga/openfga@v1.5.4-rc1/tests/listusers/listusers.go (about) 1 // Package listusers contains integration tests for the ListUsers and StreamedListUsers APIs. 2 package listusers 3 4 import ( 5 "context" 6 "fmt" 7 "math" 8 "testing" 9 10 oldparser "github.com/craigpastro/openfga-dsl-parser/v2" 11 openfgav1 "github.com/openfga/api/proto/openfga/v1" 12 parser "github.com/openfga/language/pkg/go/transformer" 13 "github.com/stretchr/testify/require" 14 "google.golang.org/grpc" 15 "google.golang.org/grpc/status" 16 "sigs.k8s.io/yaml" 17 18 "github.com/openfga/openfga/assets" 19 listuserstest "github.com/openfga/openfga/internal/test/listusers" 20 "github.com/openfga/openfga/pkg/typesystem" 21 22 "github.com/openfga/openfga/pkg/tuple" 23 "github.com/openfga/openfga/tests/check" 24 ) 25 26 var writeMaxChunkSize = 40 // chunk write requests into a chunks of this max size 27 28 type individualTest struct { 29 Name string 30 Stages []*stage 31 } 32 33 type listUsersTests struct { 34 Tests []individualTest 35 } 36 37 type stage struct { 38 Model string 39 Tuples []*openfgav1.TupleKey 40 ListUsersAssertions []*listuserstest.Assertion `json:"listUsersAssertions"` 41 } 42 43 type ClientInterface interface { 44 check.ClientInterface 45 ListUsers(ctx context.Context, in *openfgav1.ListUsersRequest, opts ...grpc.CallOption) (*openfgav1.ListUsersResponse, error) 46 } 47 48 // RunAllTests will invoke all list users tests. 49 func RunAllTests(t *testing.T, client ClientInterface) { 50 t.Run("RunAll", func(t *testing.T) { 51 t.Run("ListUsers", func(t *testing.T) { 52 t.Parallel() 53 files := []string{ 54 "tests/consolidated_1_1_tests.yaml", 55 "tests/abac_tests.yaml", 56 } 57 58 var allTestCases []individualTest 59 60 for _, file := range files { 61 var b []byte 62 var err error 63 b, err = assets.EmbedTests.ReadFile(file) 64 require.NoError(t, err) 65 66 var testCases listUsersTests 67 err = yaml.Unmarshal(b, &testCases) 68 require.NoError(t, err) 69 70 allTestCases = append(allTestCases, testCases.Tests...) 71 } 72 73 for _, test := range allTestCases { 74 test := test 75 runTest(t, test, client, false) 76 runTest(t, test, client, true) 77 } 78 }) 79 }) 80 } 81 82 func runTest(t *testing.T, test individualTest, client ClientInterface, contextTupleTest bool) { 83 ctx := context.Background() 84 name := test.Name 85 86 if contextTupleTest { 87 name += "_ctxTuples" 88 } 89 90 t.Run(name, func(t *testing.T) { 91 if contextTupleTest && len(test.Stages) > 1 { 92 // we don't want to run special contextual tuples test for these cases 93 // as multi-stages test has expectation tuples are in system 94 t.Skipf("multi-stages test has expectation tuples are in system") 95 } 96 97 t.Parallel() 98 resp, err := client.CreateStore(ctx, &openfgav1.CreateStoreRequest{Name: name}) 99 require.NoError(t, err) 100 101 storeID := resp.GetId() 102 103 for stageNumber, stage := range test.Stages { 104 t.Run(fmt.Sprintf("stage_%d", stageNumber), func(t *testing.T) { 105 if contextTupleTest && len(stage.Tuples) > 20 { 106 // https://github.com/openfga/api/blob/05de9d8be3ee12fa4e796b92dbdd4bbbf87107f2/openfga/v1/openfga.proto#L151 107 t.Skipf("cannot send more than 20 contextual tuples in one request") 108 } 109 // arrange: write model 110 var typedefs []*openfgav1.TypeDefinition 111 model, err := parser.TransformDSLToProto(stage.Model) 112 if err != nil { 113 typedefs = oldparser.MustParse(stage.Model) 114 } else { 115 typedefs = model.GetTypeDefinitions() 116 } 117 118 writeModelResponse, err := client.WriteAuthorizationModel(ctx, &openfgav1.WriteAuthorizationModelRequest{ 119 StoreId: storeID, 120 SchemaVersion: typesystem.SchemaVersion1_1, 121 TypeDefinitions: typedefs, 122 Conditions: model.GetConditions(), 123 }) 124 require.NoError(t, err) 125 126 tuples := stage.Tuples 127 tuplesLength := len(tuples) 128 // arrange: write tuples 129 if tuplesLength > 0 && !contextTupleTest { 130 for i := 0; i < tuplesLength; i += writeMaxChunkSize { 131 end := int(math.Min(float64(i+writeMaxChunkSize), float64(tuplesLength))) 132 writeChunk := (tuples)[i:end] 133 _, err = client.Write(ctx, &openfgav1.WriteRequest{ 134 StoreId: storeID, 135 AuthorizationModelId: writeModelResponse.GetAuthorizationModelId(), 136 Writes: &openfgav1.WriteRequestWrites{ 137 TupleKeys: writeChunk, 138 }, 139 }) 140 require.NoError(t, err) 141 } 142 } 143 144 if len(stage.ListUsersAssertions) == 0 { 145 t.Skipf("no list users assertions defined") 146 } 147 148 for assertionNumber, assertion := range stage.ListUsersAssertions { 149 t.Run(fmt.Sprintf("assertion_%d", assertionNumber), func(t *testing.T) { 150 detailedInfo := fmt.Sprintf("ListUsers request: %v. Model: %s. Tuples: %s. Contextual tuples: %s", assertion.Request.ToString(), stage.Model, stage.Tuples, assertion.ContextualTuples) 151 ctxTuples := assertion.ContextualTuples 152 if contextTupleTest { 153 ctxTuples = append(ctxTuples, stage.Tuples...) 154 } 155 156 // assert 1: on regular list users endpoint 157 convertedRequest := assertion.Request.ToProtoRequest() 158 resp, err := client.ListUsers(ctx, &openfgav1.ListUsersRequest{ 159 StoreId: storeID, 160 AuthorizationModelId: writeModelResponse.GetAuthorizationModelId(), 161 Object: convertedRequest.GetObject(), 162 Relation: convertedRequest.GetRelation(), 163 UserFilters: convertedRequest.GetUserFilters(), 164 Context: assertion.Context, 165 ContextualTuples: ctxTuples, 166 }) 167 if assertion.ErrorCode != 0 && len(assertion.Expectation) > 0 { 168 t.Errorf("cannot have a test with the expectation of both an error code and a result") 169 } 170 171 if assertion.ErrorCode == 0 { 172 require.NoError(t, err, detailedInfo) 173 require.ElementsMatch(t, assertion.Expectation, listuserstest.FromProtoResponse(resp), detailedInfo) 174 175 // assert 2: each user in the response of ListUsers should return check -> true 176 for _, user := range resp.GetUsers() { 177 checkRequestTupleKey := tuple.NewCheckRequestTupleKey(assertion.Request.Object, assertion.Request.Relation, tuple.UserProtoToString(user)) 178 checkResp, err := client.Check(ctx, &openfgav1.CheckRequest{ 179 StoreId: storeID, 180 TupleKey: checkRequestTupleKey, 181 AuthorizationModelId: writeModelResponse.GetAuthorizationModelId(), 182 ContextualTuples: &openfgav1.ContextualTupleKeys{ 183 TupleKeys: ctxTuples, 184 }, 185 Context: assertion.Context, 186 }) 187 require.NoError(t, err, detailedInfo) 188 require.True(t, checkResp.GetAllowed(), "Expected allowed = true", checkRequestTupleKey) 189 } 190 } else { 191 require.Error(t, err, detailedInfo) 192 e, ok := status.FromError(err) 193 require.True(t, ok, detailedInfo) 194 require.Equal(t, assertion.ErrorCode, int(e.Code()), detailedInfo) 195 } 196 }) 197 } 198 }) 199 } 200 }) 201 }