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  }