Skip to content

Commit

Permalink
fix: empty coninuation token means last page (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
adriantam committed Jul 6, 2023
2 parents 31ae401 + 3be47c7 commit 48dba47
Show file tree
Hide file tree
Showing 6 changed files with 653 additions and 80 deletions.
2 changes: 1 addition & 1 deletion cmd/models/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
3 changes: 2 additions & 1 deletion cmd/models/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
73 changes: 41 additions & 32 deletions cmd/stores/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"context"
"encoding/json"
"fmt"
"os"

"github.com/openfga/cli/lib/cmd-utils"
openfga "github.com/openfga/go-sdk"
Expand All @@ -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
},
}

Expand Down
246 changes: 246 additions & 0 deletions cmd/stores/list_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading

0 comments on commit 48dba47

Please sign in to comment.