diff --git a/cmd/models/list.go b/cmd/models/list.go index 246d48c..9da1e26 100644 --- a/cmd/models/list.go +++ b/cmd/models/list.go @@ -51,7 +51,7 @@ func listModels(fgaClient client.SdkClient, maxPages int) (string, error) { pageIndex++ - if response.ContinuationToken == nil || *response.ContinuationToken == continuationToken || pageIndex > maxPages { + if response.ContinuationToken == nil || *response.ContinuationToken == "" || pageIndex > maxPages { break } diff --git a/cmd/models/list_test.go b/cmd/models/list_test.go index d752e6d..b780387 100644 --- a/cmd/models/list_test.go +++ b/cmd/models/list_test.go @@ -159,9 +159,10 @@ func TestListModelsMultiPage(t *testing.T) { models2 := []openfga.AuthorizationModel{ model2, } + emptyToken := "" response2 := openfga.ReadAuthorizationModelsResponse{ AuthorizationModels: &models2, - ContinuationToken: continuationToken1, + ContinuationToken: &emptyToken, } gomock.InOrder( diff --git a/cmd/stores/list.go b/cmd/stores/list.go index 37431e1..17e1e19 100644 --- a/cmd/stores/list.go +++ b/cmd/stores/list.go @@ -19,7 +19,6 @@ import ( "context" "encoding/json" "fmt" - "os" "github.com/openfga/cli/lib/cmd-utils" openfga "github.com/openfga/go-sdk" @@ -30,51 +29,61 @@ import ( // MaxStoresPagesLength Limit the pages of stores so that we are not paginating indefinitely. var MaxStoresPagesLength = 20 // up to 1000 records +func listStores(fgaClient client.SdkClient, maxPages int) (string, error) { + stores := []openfga.Store{} + continuationToken := "" + pageIndex := 0 + + for { + options := client.ClientListStoresOptions{ + ContinuationToken: &continuationToken, + } + + response, err := fgaClient.ListStores(context.Background()).Options(options).Execute() + if err != nil { + return "", fmt.Errorf("failed to list stores due to %w", err) + } + + stores = append(stores, *response.Stores...) + pageIndex++ + + if response.ContinuationToken == nil || *response.ContinuationToken == "" || pageIndex >= maxPages { + break + } + + continuationToken = *response.ContinuationToken + } + + storesJSON, err := json.Marshal(openfga.ListStoresResponse{Stores: &stores}) + if err != nil { + return "", fmt.Errorf("failed to list stores due to %w", err) + } + + return string(storesJSON), nil +} + // listCmd represents the list command. var listCmd = &cobra.Command{ Use: "list", Short: "List stores", Long: `Get a list of stores.`, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { clientConfig := cmdutils.GetClientConfig(cmd) fgaClient, err := clientConfig.GetFgaClient() if err != nil { - fmt.Printf("Failed to initialize FGA Client due to %v", err) - os.Exit(1) + return fmt.Errorf("failed to initialize FGA Client due to %w", err) } maxPages, _ := cmd.Flags().GetInt("max-pages") if err != nil { - fmt.Printf("Failed to list models due to %v", err) - os.Exit(1) - } - stores := []openfga.Store{} - continuationToken := "" - pageIndex := 0 - for { - options := client.ClientListStoresOptions{ - ContinuationToken: &continuationToken, - } - response, err := fgaClient.ListStores(context.Background()).Options(options).Execute() - if err != nil { - fmt.Printf("Failed to list stores due to %v", err) - os.Exit(1) - } - - stores = append(stores, *response.Stores...) - pageIndex++ - if response.ContinuationToken == nil || *response.ContinuationToken == continuationToken || pageIndex >= maxPages { - break - } - - continuationToken = *response.ContinuationToken + return fmt.Errorf("failed to list models due to %w", err) } - - storesJSON, err := json.Marshal(openfga.ListStoresResponse{Stores: &stores}) + output, err := listStores(fgaClient, maxPages) if err != nil { - fmt.Printf("Failed to list stores due to %v", err) - os.Exit(1) + return err } - fmt.Print(string(storesJSON)) + fmt.Print(output) + + return nil }, } diff --git a/cmd/stores/list_test.go b/cmd/stores/list_test.go new file mode 100644 index 0000000..2915a3f --- /dev/null +++ b/cmd/stores/list_test.go @@ -0,0 +1,246 @@ +package stores + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/golang/mock/gomock" + mockclient "github.com/openfga/cli/mocks" + openfga "github.com/openfga/go-sdk" + "github.com/openfga/go-sdk/client" +) + +var errMockListStores = errors.New("mock error") + +func TestListStoresError(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockFgaClient := mockclient.NewMockSdkClient(mockCtrl) + + mockExecute := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + + var response openfga.ListStoresResponse + + mockExecute.EXPECT().Execute().Return(&response, errMockListStores) + + mockRequest := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + options := client.ClientListStoresOptions{ + ContinuationToken: openfga.PtrString(""), + } + mockRequest.EXPECT().Options(options).Return(mockExecute) + mockFgaClient.EXPECT().ListStores(context.Background()).Return(mockRequest) + + _, err := listStores(mockFgaClient, 5) + if err == nil { + t.Error("Expect error but there is none") + } +} + +func TestListStoresEmpty(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockFgaClient := mockclient.NewMockSdkClient(mockCtrl) + + mockExecute := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + + var stores []openfga.Store + + response := openfga.ListStoresResponse{ + Stores: &stores, + ContinuationToken: openfga.PtrString(""), + } + mockExecute.EXPECT().Execute().Return(&response, nil) + + mockRequest := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + options := client.ClientListStoresOptions{ + ContinuationToken: openfga.PtrString(""), + } + mockRequest.EXPECT().Options(options).Return(mockExecute) + mockFgaClient.EXPECT().ListStores(context.Background()).Return(mockRequest) + + output, err := listStores(mockFgaClient, 5) + if err != nil { + t.Error(err) + } + + expectedOutput := "{\"stores\":[]}" + if output != expectedOutput { + t.Errorf("Expected output %v actual %v", expectedOutput, output) + } +} + +func TestListStoresSinglePage(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockFgaClient := mockclient.NewMockSdkClient(mockCtrl) + + mockExecute := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + + expectedTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + + stores := []openfga.Store{ + { + Id: openfga.PtrString("12345"), + Name: openfga.PtrString("foo"), + CreatedAt: &expectedTime, + UpdatedAt: &expectedTime, + }, + } + + response := openfga.ListStoresResponse{ + Stores: &stores, + ContinuationToken: openfga.PtrString(""), + } + mockExecute.EXPECT().Execute().Return(&response, nil) + + mockRequest := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + options := client.ClientListStoresOptions{ + ContinuationToken: openfga.PtrString(""), + } + mockRequest.EXPECT().Options(options).Return(mockExecute) + mockFgaClient.EXPECT().ListStores(context.Background()).Return(mockRequest) + + output, err := listStores(mockFgaClient, 5) + if err != nil { + t.Error(err) + } + + expectedOutput := "{\"stores\":[{\"created_at\":\"2009-11-10T23:00:00Z\",\"id\":\"12345\",\"name\":\"foo\",\"updated_at\":\"2009-11-10T23:00:00Z\"}]}" //nolint:lll + if output != expectedOutput { + t.Errorf("Expected output %v actual %v", expectedOutput, output) + } +} + +func TestListStoresMultiPage(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + const continuationToken = "01GXSA8YR785C4FYS3C0RTG7B2" //nolint:gosec + + mockFgaClient := mockclient.NewMockSdkClient(mockCtrl) + + mockExecute1 := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + + expectedTime1 := time.Date(2009, time.November, 10, 22, 0, 0, 0, time.UTC) + + stores1 := []openfga.Store{ + { + Id: openfga.PtrString("abcde"), + Name: openfga.PtrString("moo"), + CreatedAt: &expectedTime1, + UpdatedAt: &expectedTime1, + }, + } + + response1 := openfga.ListStoresResponse{ + Stores: &stores1, + ContinuationToken: openfga.PtrString(continuationToken), + } + + mockExecute2 := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + + expectedTime2 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + + stores2 := []openfga.Store{ + { + Id: openfga.PtrString("12345"), + Name: openfga.PtrString("foo"), + CreatedAt: &expectedTime2, + UpdatedAt: &expectedTime2, + }, + } + + response2 := openfga.ListStoresResponse{ + Stores: &stores2, + ContinuationToken: openfga.PtrString(""), + } + gomock.InOrder( + mockExecute1.EXPECT().Execute().Return(&response1, nil), + mockExecute2.EXPECT().Execute().Return(&response2, nil), + ) + + mockRequest1 := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + options1 := client.ClientListStoresOptions{ + ContinuationToken: openfga.PtrString(""), + } + mockRequest2 := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + options2 := client.ClientListStoresOptions{ + ContinuationToken: openfga.PtrString(continuationToken), + } + gomock.InOrder( + mockRequest1.EXPECT().Options(options1).Return(mockExecute1), + mockRequest2.EXPECT().Options(options2).Return(mockExecute2), + ) + gomock.InOrder( + mockFgaClient.EXPECT().ListStores(context.Background()).Return(mockRequest1), + mockFgaClient.EXPECT().ListStores(context.Background()).Return(mockRequest2), + ) + + output, err := listStores(mockFgaClient, 5) + if err != nil { + t.Error(err) + } + + expectedOutput := "{\"stores\":[{\"created_at\":\"2009-11-10T22:00:00Z\",\"id\":\"abcde\",\"name\":\"moo\",\"updated_at\":\"2009-11-10T22:00:00Z\"},{\"created_at\":\"2009-11-10T23:00:00Z\",\"id\":\"12345\",\"name\":\"foo\",\"updated_at\":\"2009-11-10T23:00:00Z\"}]}" //nolint:lll + if output != expectedOutput { + t.Errorf("Expected output %v actual %v", expectedOutput, output) + } +} + +func TestListStoresMultiPageMaxPage(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + const continuationToken = "01GXSA8YR785C4FYS3C0RTG7B2" //nolint:gosec + + mockFgaClient := mockclient.NewMockSdkClient(mockCtrl) + + mockExecute1 := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + + expectedTime1 := time.Date(2009, time.November, 10, 22, 0, 0, 0, time.UTC) + + stores1 := []openfga.Store{ + { + Id: openfga.PtrString("abcde"), + Name: openfga.PtrString("moo"), + CreatedAt: &expectedTime1, + UpdatedAt: &expectedTime1, + }, + } + + response1 := openfga.ListStoresResponse{ + Stores: &stores1, + ContinuationToken: openfga.PtrString(continuationToken), + } + + mockExecute1.EXPECT().Execute().Return(&response1, nil) + + mockRequest1 := mockclient.NewMockSdkClientListStoresRequestInterface(mockCtrl) + options1 := client.ClientListStoresOptions{ + ContinuationToken: openfga.PtrString(""), + } + mockRequest1.EXPECT().Options(options1).Return(mockExecute1) + mockFgaClient.EXPECT().ListStores(context.Background()).Return(mockRequest1) + + output, err := listStores(mockFgaClient, 1) + if err != nil { + t.Error(err) + } + + expectedOutput := "{\"stores\":[{\"created_at\":\"2009-11-10T22:00:00Z\",\"id\":\"abcde\",\"name\":\"moo\",\"updated_at\":\"2009-11-10T22:00:00Z\"}]}" //nolint:lll + if output != expectedOutput { + t.Errorf("Expected output %v actual %v", expectedOutput, output) + } +} diff --git a/cmd/tuples/read.go b/cmd/tuples/read.go index 42bc39d..08d1696 100644 --- a/cmd/tuples/read.go +++ b/cmd/tuples/read.go @@ -19,7 +19,6 @@ import ( "context" "encoding/json" "fmt" - "os" cmdutils "github.com/openfga/cli/lib/cmd-utils" openfga "github.com/openfga/go-sdk" @@ -30,69 +29,81 @@ import ( // MaxReadPagesLength Limit the tuples so that we are not paginating indefinitely. var MaxReadPagesLength = 20 +func read(fgaClient client.SdkClient, user string, relation string, object string, maxPages int) (string, error) { + body := &client.ClientReadRequest{} + if user != "" { + body.User = &user + } + + if relation != "" { + body.Relation = &relation + } + + if object != "" { + body.Object = &object + } + + tuples := make([]openfga.Tuple, 0) + + continuationToken := "" + pageIndex := 0 + options := client.ClientReadOptions{} + + for { + options.ContinuationToken = &continuationToken + + response, err := fgaClient.Read(context.Background()).Body(*body).Options(options).Execute() + if err != nil { + return "", fmt.Errorf("failed to read tuples due to %w", err) + } + + tuples = append(tuples, *response.Tuples...) + pageIndex++ + + if response.ContinuationToken == nil || *response.ContinuationToken == "" || pageIndex >= maxPages { + break + } + + continuationToken = *response.ContinuationToken + } + + tuplesJSON, err := json.Marshal(openfga.ReadResponse{Tuples: &tuples}) + if err != nil { + return "", fmt.Errorf("failed to read tuples due to %w", err) + } + + return string(tuplesJSON), nil +} + // readCmd represents the read command. var readCmd = &cobra.Command{ Use: "read", Short: "Read Relationship Tuples", - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { clientConfig := cmdutils.GetClientConfig(cmd) + fgaClient, err := clientConfig.GetFgaClient() if err != nil { - fmt.Printf("Failed to initialize FGA Client due to %v", err) - os.Exit(1) + return fmt.Errorf("failed to initialize FGA Client due to %w", err) } + user, _ := cmd.Flags().GetString("user") relation, _ := cmd.Flags().GetString("relation") object, _ := cmd.Flags().GetString("object") - if err != nil { - fmt.Printf("Failed to read tuples due to %v", err) - os.Exit(1) - } maxPages, _ := cmd.Flags().GetInt("max-pages") if err != nil { - fmt.Printf("Failed to read tuples due to %v", err) - os.Exit(1) + return fmt.Errorf("failed to read tuples due to %w", err) } - body := &client.ClientReadRequest{} - if user != "" { - body.User = &user - } - if relation != "" { - body.Relation = &relation - } - if object != "" { - body.Object = &object + output, err := read(fgaClient, user, relation, object, maxPages) + if err != nil { + return err } - tuples := []openfga.Tuple{} - continuationToken := "" - pageIndex := 0 - options := client.ClientReadOptions{} - for { - options.ContinuationToken = &continuationToken - response, err := fgaClient.Read(context.Background()).Body(*body).Options(options).Execute() - if err != nil { - fmt.Printf("Failed to read tuples due to %v", err) - os.Exit(1) - } - - tuples = append(tuples, *response.Tuples...) - pageIndex++ - if response.ContinuationToken == nil || *response.ContinuationToken == continuationToken || pageIndex >= maxPages { - break - } - - continuationToken = *response.ContinuationToken - } + fmt.Print(output) - tuplesJSON, err := json.Marshal(openfga.ReadResponse{Tuples: &tuples}) - if err != nil { - fmt.Printf("Failed to read tuples due to %v", err) - os.Exit(1) - } - fmt.Print(string(tuplesJSON)) + return nil }, } @@ -100,5 +111,5 @@ func init() { readCmd.Flags().String("user", "", "User") readCmd.Flags().String("relation", "", "Relation") readCmd.Flags().String("object", "", "Object") - readCmd.Flags().Int("max-pages", MaxReadChangesPagesLength, "Max number of pages to get.") + readCmd.Flags().Int("max-pages", MaxReadPagesLength, "Max number of pages to get.") } diff --git a/cmd/tuples/read_test.go b/cmd/tuples/read_test.go new file mode 100644 index 0000000..2800583 --- /dev/null +++ b/cmd/tuples/read_test.go @@ -0,0 +1,306 @@ +package tuples + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/golang/mock/gomock" + mock_client "github.com/openfga/cli/mocks" + openfga "github.com/openfga/go-sdk" + "github.com/openfga/go-sdk/client" +) + +var errMockRead = errors.New("mock error") + +func TestReadError(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockFgaClient := mock_client.NewMockSdkClient(mockCtrl) + + mockExecute := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + + var response openfga.ReadResponse + + mockExecute.EXPECT().Execute().Return(&response, errMockRead) + + mockRequest := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + options := client.ClientReadOptions{ + ContinuationToken: openfga.PtrString(""), + } + mockRequest.EXPECT().Options(options).Return(mockExecute) + + mockBody := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + + body := client.ClientReadRequest{ + User: openfga.PtrString("user:user1"), + Relation: openfga.PtrString("reader"), + Object: openfga.PtrString("document:doc1"), + } + mockBody.EXPECT().Body(body).Return(mockRequest) + + mockFgaClient.EXPECT().Read(context.Background()).Return(mockBody) + + _, err := read(mockFgaClient, "user:user1", "reader", "document:doc1", 5) + if err == nil { + t.Error("Expect error but there is none") + } +} + +func TestReadEmpty(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockFgaClient := mock_client.NewMockSdkClient(mockCtrl) + + mockExecute := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + + var tuples []openfga.Tuple + response := openfga.ReadResponse{ + Tuples: &tuples, + ContinuationToken: openfga.PtrString(""), + } + + mockExecute.EXPECT().Execute().Return(&response, nil) + + mockRequest := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + options := client.ClientReadOptions{ + ContinuationToken: openfga.PtrString(""), + } + mockRequest.EXPECT().Options(options).Return(mockExecute) + + mockBody := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + + body := client.ClientReadRequest{ + User: openfga.PtrString("user:user1"), + Relation: openfga.PtrString("reader"), + Object: openfga.PtrString("document:doc1"), + } + mockBody.EXPECT().Body(body).Return(mockRequest) + + mockFgaClient.EXPECT().Read(context.Background()).Return(mockBody) + + output, err := read(mockFgaClient, "user:user1", "reader", "document:doc1", 5) + if err != nil { + t.Error(err) + } + + expectedOutput := "{\"tuples\":[]}" + if output != expectedOutput { + t.Errorf("Expected output %v actual %v", expectedOutput, output) + } +} + +func TestReadSinglePage(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockFgaClient := mock_client.NewMockSdkClient(mockCtrl) + + mockExecute := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + + key1 := openfga.NewTupleKey() + key1.Object = openfga.PtrString("document:doc1") + key1.Relation = openfga.PtrString("reader") + key1.User = openfga.PtrString("user:user1") + + changesTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + + tuples := []openfga.Tuple{ + { + Key: key1, + Timestamp: &changesTime, + }, + } + response := openfga.ReadResponse{ + Tuples: &tuples, + ContinuationToken: openfga.PtrString(""), + } + + mockExecute.EXPECT().Execute().Return(&response, nil) + + mockRequest := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + options := client.ClientReadOptions{ + ContinuationToken: openfga.PtrString(""), + } + mockRequest.EXPECT().Options(options).Return(mockExecute) + + mockBody := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + + body := client.ClientReadRequest{ + User: openfga.PtrString("user:user1"), + Relation: openfga.PtrString("reader"), + Object: openfga.PtrString("document:doc1"), + } + mockBody.EXPECT().Body(body).Return(mockRequest) + + mockFgaClient.EXPECT().Read(context.Background()).Return(mockBody) + + output, err := read(mockFgaClient, "user:user1", "reader", "document:doc1", 5) + if err != nil { + t.Error(err) + } + + expectedOutput := "{\"tuples\":[{\"key\":{\"object\":\"document:doc1\",\"relation\":\"reader\",\"user\":\"user:user1\"},\"timestamp\":\"2009-11-10T23:00:00Z\"}]}" //nolint:lll + if output != expectedOutput { + t.Errorf("Expected output %v actual %v", expectedOutput, output) + } +} + +func TestReadMultiPages(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockFgaClient := mock_client.NewMockSdkClient(mockCtrl) + + const continuationToken = "01GXSA8YR785C4FYS3C0RTG7B2" //nolint:gosec + + mockExecute1 := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + + key1 := openfga.NewTupleKey() + key1.Object = openfga.PtrString("document:doc1") + key1.Relation = openfga.PtrString("reader") + key1.User = openfga.PtrString("user:user1") + + changesTime1 := time.Date(2009, time.November, 10, 22, 0, 0, 0, time.UTC) + + tuples1 := []openfga.Tuple{ + { + Key: key1, + Timestamp: &changesTime1, + }, + } + response1 := openfga.ReadResponse{ + Tuples: &tuples1, + ContinuationToken: openfga.PtrString(continuationToken), + } + + mockExecute2 := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + + key2 := openfga.NewTupleKey() + key2.Object = openfga.PtrString("document:doc2") + key2.Relation = openfga.PtrString("reader") + key2.User = openfga.PtrString("user:user1") + + changesTime2 := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + + tuples2 := []openfga.Tuple{ + { + Key: key2, + Timestamp: &changesTime2, + }, + } + response2 := openfga.ReadResponse{ + Tuples: &tuples2, + ContinuationToken: openfga.PtrString(""), + } + + gomock.InOrder( + mockExecute1.EXPECT().Execute().Return(&response1, nil), + mockExecute2.EXPECT().Execute().Return(&response2, nil), + ) + + mockRequest1 := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + options1 := client.ClientReadOptions{ + ContinuationToken: openfga.PtrString(""), + } + mockRequest2 := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + options2 := client.ClientReadOptions{ + ContinuationToken: openfga.PtrString(continuationToken), + } + gomock.InOrder( + mockRequest1.EXPECT().Options(options1).Return(mockExecute1), + mockRequest2.EXPECT().Options(options2).Return(mockExecute2), + ) + + mockBody1 := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + mockBody2 := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + + body := client.ClientReadRequest{ + User: openfga.PtrString("user:user1"), + Relation: openfga.PtrString("reader"), + Object: openfga.PtrString("document:doc1"), + } + gomock.InOrder( + mockBody1.EXPECT().Body(body).Return(mockRequest1), + mockBody2.EXPECT().Body(body).Return(mockRequest2), + ) + + gomock.InOrder( + mockFgaClient.EXPECT().Read(context.Background()).Return(mockBody1), + mockFgaClient.EXPECT().Read(context.Background()).Return(mockBody2), + ) + + output, err := read(mockFgaClient, "user:user1", "reader", "document:doc1", 5) + if err != nil { + t.Error(err) + } + + expectedOutput := `{"tuples":[{"key":{"object":"document:doc1","relation":"reader","user":"user:user1"},"timestamp":"2009-11-10T22:00:00Z"},{"key":{"object":"document:doc2","relation":"reader","user":"user:user1"},"timestamp":"2009-11-10T23:00:00Z"}]}` //nolint:lll + if output != expectedOutput { + t.Errorf("Expected output %v actual %v", expectedOutput, output) + } +} + +func TestReadMultiPagesMaxLimit(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockFgaClient := mock_client.NewMockSdkClient(mockCtrl) + + mockExecute := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + + key1 := openfga.NewTupleKey() + key1.Object = openfga.PtrString("document:doc1") + key1.Relation = openfga.PtrString("reader") + key1.User = openfga.PtrString("user:user1") + + changesTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + + tuples := []openfga.Tuple{ + { + Key: key1, + Timestamp: &changesTime, + }, + } + response := openfga.ReadResponse{ + Tuples: &tuples, + ContinuationToken: openfga.PtrString("ABCDEFG"), + } + + mockExecute.EXPECT().Execute().Return(&response, nil) + + mockRequest := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + options := client.ClientReadOptions{ + ContinuationToken: openfga.PtrString(""), + } + mockRequest.EXPECT().Options(options).Return(mockExecute) + + mockBody := mock_client.NewMockSdkClientReadRequestInterface(mockCtrl) + + body := client.ClientReadRequest{ + User: openfga.PtrString("user:user1"), + Relation: openfga.PtrString("reader"), + Object: openfga.PtrString("document:doc1"), + } + mockBody.EXPECT().Body(body).Return(mockRequest) + + mockFgaClient.EXPECT().Read(context.Background()).Return(mockBody) + + output, err := read(mockFgaClient, "user:user1", "reader", "document:doc1", 1) + if err != nil { + t.Error(err) + } + + expectedOutput := "{\"tuples\":[{\"key\":{\"object\":\"document:doc1\",\"relation\":\"reader\",\"user\":\"user:user1\"},\"timestamp\":\"2009-11-10T23:00:00Z\"}]}" //nolint:lll + if output != expectedOutput { + t.Errorf("Expected output %v actual %v", expectedOutput, output) + } +}