Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions cmd/store/.test-data/failed-store-import-001.fga.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: FGA Demo Store
model: |+
model
schema 1.1

type user

type doc
relations
define owner: [user]
define reader: [user]
define writer: [user]

tuples:
- user: user:Kross
relation: writer
object: doc:dev-docs
- user: user:Modric
relation: owner
object: document:wrong-type
tests:
- name: Tests
check: []
4 changes: 2 additions & 2 deletions cmd/store/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
)

type CreateStoreAndModelResponse struct {
Store client.ClientCreateStoreResponse `json:"store"`
Store *client.ClientCreateStoreResponse `json:"store,omitempty"`
Model *client.ClientWriteAuthorizationModelResponse `json:"model,omitempty"`
}

Expand Down Expand Up @@ -69,7 +69,7 @@ func CreateStoreWithModel(
return nil, err
}

response.Store = *createStoreResponse
response.Store = createStoreResponse
fgaClient.SetStoreId(response.Store.Id)

if inputModel != "" {
Expand Down
76 changes: 60 additions & 16 deletions cmd/store/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,55 @@ import (
"github.com/openfga/cli/internal/storetest"
)

// importStoreIODependencies defines IO dependencies for importing store
type importStoreIODependencies struct {
createStoreWithModel func(
clientConfig fga.ClientConfig,
storeName string,
inputModel string,
inputFormat authorizationmodel.ModelFormat,
) (*CreateStoreAndModelResponse, error)
importTuples func(
fgaClient client.SdkClient,
body client.ClientWriteRequest,
maxTuplesPerWrite int,
maxParallelRequests int,
) (*tuple.ImportResponse, error)
modelWrite func(
fgaClient client.SdkClient,
inputModel authorizationmodel.AuthzModel,
) (*client.ClientWriteAuthorizationModelResponse, error)
}

type ImportStoreResponse struct {
*CreateStoreAndModelResponse
Tuple *tuple.ImportResponse `json:"tuple"`
}

func importStore(
clientConfig fga.ClientConfig,
fgaClient client.SdkClient,
storeData *storetest.StoreData,
format authorizationmodel.ModelFormat,
storeID string,
maxTuplesPerWrite int,
maxParallelRequests int,
) (*CreateStoreAndModelResponse, error) {
ioAggregator importStoreIODependencies,
) (*ImportStoreResponse, error) {
var err error
var response *CreateStoreAndModelResponse //nolint:wsl
if storeID == "" { //nolint:wsl
createStoreAndModelResponse, err := CreateStoreWithModel(clientConfig, storeData.Name, storeData.Model, format)
response = createStoreAndModelResponse

var fgaClient client.SdkClient

response := &ImportStoreResponse{
CreateStoreAndModelResponse: &CreateStoreAndModelResponse{},
}
if storeID == "" { //nolint:wsl
createStoreAndModelResponse, err := ioAggregator.createStoreWithModel(
clientConfig,
storeData.Name,
storeData.Model,
format,
)
response.CreateStoreAndModelResponse = createStoreAndModelResponse
if err != nil { //nolint:wsl
return nil, err
}
Expand All @@ -60,10 +95,17 @@ func importStore(
return nil, err //nolint:wrapcheck
}

_, err := model.Write(fgaClient, authModel)
fgaClient, err = clientConfig.GetFgaClient()
if err != nil {
return nil, fmt.Errorf("failed to initialize FGA Client due to %w", err)
}

authorizationModelResponse, err := ioAggregator.modelWrite(fgaClient, authModel)
if err != nil {
return nil, fmt.Errorf("failed to write model due to %w", err)
}

response.Model = authorizationModelResponse
}

fgaClient, err = clientConfig.GetFgaClient()
Expand All @@ -75,11 +117,13 @@ func importStore(
Writes: storeData.Tuples,
}

_, err = tuple.ImportTuples(fgaClient, writeRequest, maxTuplesPerWrite, maxParallelRequests)
importTupleResponse, err := ioAggregator.importTuples(fgaClient, writeRequest, maxTuplesPerWrite, maxParallelRequests)
if err != nil {
return nil, err //nolint:wrapcheck
return nil, err
}

response.Tuple = importTupleResponse

return response, nil
}

Expand All @@ -90,7 +134,7 @@ var importCmd = &cobra.Command{
Long: `Import a store: updating the name, model and appending the global tuples`,
Example: "fga store import --file=model.fga.yaml",
RunE: func(cmd *cobra.Command, _ []string) error {
var createStoreAndModelResponse *CreateStoreAndModelResponse
var createStoreAndModelResponse *ImportStoreResponse
clientConfig := cmdutils.GetClientConfig(cmd)

storeID, err := cmd.Flags().GetString("store-id")
Expand Down Expand Up @@ -118,13 +162,13 @@ var importCmd = &cobra.Command{
return err //nolint:wrapcheck
}

fgaClient, err := clientConfig.GetFgaClient()
if err != nil {
return fmt.Errorf("failed to initialize FGA Client due to %w", err)
ioAggregator := importStoreIODependencies{
createStoreWithModel: CreateStoreWithModel,
importTuples: tuple.ImportTuples,
modelWrite: model.Write,
}

createStoreAndModelResponse, err = importStore(clientConfig, fgaClient, storeData, format,
storeID, maxTuplesPerWrite, maxParallelRequests)
createStoreAndModelResponse, err = importStore(clientConfig, storeData, format,
storeID, maxTuplesPerWrite, maxParallelRequests, ioAggregator)
if err != nil {
return err
}
Expand Down
206 changes: 206 additions & 0 deletions cmd/store/import_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package store

import (
"fmt"
"github.com/openfga/cli/cmd/tuple"
"github.com/openfga/cli/internal/authorizationmodel"
"github.com/openfga/cli/internal/fga"
"github.com/openfga/cli/internal/storetest"
"github.com/openfga/go-sdk/client"
"path"
"reflect"
"testing"
"time"
)

func TestImportStore(t *testing.T) {
t.Run("Must create store and modelID when "+
"there's no store configured", func(t *testing.T) {
t.Parallel()

clientConfig := fga.ClientConfig{ApiUrl: "https://localhost:8080"}
storeData := &storetest.StoreData{}
authorizationModelID := "01HWJGBQQNNQATBQ661SH6585Y001"
storeID := "Test-001"

ioAggregator := importStoreIODependencies{
importTuples: func(_ client.SdkClient, _ client.ClientWriteRequest, _, _ int,
) (*tuple.ImportResponse, error) {
return &tuple.ImportResponse{}, nil
},
createStoreWithModel: func(_ fga.ClientConfig, _, _ string, _ authorizationmodel.ModelFormat,
) (*CreateStoreAndModelResponse, error) {
return &CreateStoreAndModelResponse{
Store: &client.ClientCreateStoreResponse{
Id: "01HWJGBQQHZZJHQEQZ6MCBC3B0",
Name: storeID,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
Model: &client.ClientWriteAuthorizationModelResponse{
AuthorizationModelId: authorizationModelID,
},
}, nil
},
}

response, err := importStore(clientConfig,
storeData,
"",
"",
2,
2,
ioAggregator,
)
// Assert
if err != nil {
t.Error(err)
}

if response.Model.AuthorizationModelId != authorizationModelID {
t.Fatalf("expected: %s\nreturned: %s", authorizationModelID, response.Model.AuthorizationModelId)
}

if response.Store.Name != storeID {
t.Fatalf("expected: %s\nreturned: %s", storeID, response.Store.Name)
}
})

t.Run("Must return the Model ID and an empty Store "+
"when importing into an existing store", func(t *testing.T) {
t.Parallel()

clientConfig := fga.ClientConfig{ApiUrl: "https://localhost:8080"}
storeData := &storetest.StoreData{}
authorizationModelID := "01HWJGBQQNNQATBQ661SH6585Y002"

ioAggregator := importStoreIODependencies{
importTuples: func(_ client.SdkClient, _ client.ClientWriteRequest, _, _ int,
) (*tuple.ImportResponse, error) {
return &tuple.ImportResponse{}, nil
},
modelWrite: func(_ client.SdkClient, _ authorizationmodel.AuthzModel) (*client.ClientWriteAuthorizationModelResponse, error) {
return &client.ClientWriteAuthorizationModelResponse{
AuthorizationModelId: authorizationModelID,
}, nil
},
createStoreWithModel: func(_ fga.ClientConfig, _, _ string, _ authorizationmodel.ModelFormat,
) (*CreateStoreAndModelResponse, error) {
return &CreateStoreAndModelResponse{
Model: &client.ClientWriteAuthorizationModelResponse{
AuthorizationModelId: authorizationModelID,
},
}, nil
},
}

response, err := importStore(clientConfig,
storeData, "",
"01HWJGBQQHZZJHQEQZ6MCBC3B0",
2,
2,
ioAggregator,
)
// Assert
if err != nil {
t.Error(err)
}

if response.Store != nil {
t.Fatalf("Expected: null\nReturn: %v", response.Store)
}

if response.Model.AuthorizationModelId != authorizationModelID {
t.Fatalf("expected: %s\nreturned: %s", authorizationModelID, response.Model.AuthorizationModelId)
}
})

t.Run("Must returns the modelID, a null store object, and a list of "+
"failed/successfully imported tuples when tuples containing unregistered "+
"types are provided as input", func(t *testing.T) {
t.Parallel()

clientConfig := fga.ClientConfig{ApiUrl: "https://localhost:8080"}
fileName := "./.test-data/failed-store-import-001.fga.yaml"
format, storeData, _ := storetest.ReadFromFile(fileName, path.Dir(fileName))
authorizationModelID := "01HWJGBQQNNQATBQ661SH6585Y003"

successfulTupleImport := storeData.Tuples[0]
failedTupleImport := storeData.Tuples[1]
failureReason := fmt.Sprintf("error message: Invalid tuple '%s#%s@%s'. Reason: type 'document' not found",
failedTupleImport.Object,
failedTupleImport.Relation,
failedTupleImport.User,
)
ioAggregator := importStoreIODependencies{
importTuples: func(_ client.SdkClient,
_ client.ClientWriteRequest, _,
_ int,
) (*tuple.ImportResponse, error) {
return &tuple.ImportResponse{
Successful: []client.ClientTupleKey{
successfulTupleImport,
},
Failed: []tuple.FailedWriteResponse{
{
TupleKey: failedTupleImport,
Reason: failureReason,
},
},
}, nil
},
modelWrite: func(
_ client.SdkClient,
_ authorizationmodel.AuthzModel,
) (*client.ClientWriteAuthorizationModelResponse, error) {
return &client.ClientWriteAuthorizationModelResponse{
AuthorizationModelId: authorizationModelID,
}, nil
},
createStoreWithModel: func(_ fga.ClientConfig,
_,
_ string,
_ authorizationmodel.ModelFormat,
) (*CreateStoreAndModelResponse, error) {
return &CreateStoreAndModelResponse{
Model: &client.ClientWriteAuthorizationModelResponse{
AuthorizationModelId: authorizationModelID,
},
}, nil
},
}

// Act
importResponse, err := importStore(clientConfig,
storeData,
format,
"01HWJGBQQHZZJHQEQZ6MCBC3B0",
2,
2,
ioAggregator,
)
if err != nil {
t.Error(err)
}

if len(importResponse.Tuple.Successful) != 1 {
t.Fatalf("expected: %d\nreturned: %d", 1, len(importResponse.Tuple.Successful))
}

if len(importResponse.Tuple.Failed) != 1 {
t.Fatalf("expected: %d\nreturned: %d", 1, len(importResponse.Tuple.Failed))
}

if !reflect.DeepEqual(importResponse.Tuple.Successful[0], successfulTupleImport) {
t.Fatalf("expected: %v\nreturned: %v", successfulTupleImport, importResponse.Tuple.Successful[0])
}

if !reflect.DeepEqual(importResponse.Tuple.Failed[0].TupleKey, failedTupleImport) {
t.Fatalf("expected: %v\nreturned: %v", failedTupleImport, importResponse.Tuple.Failed[0])
}

if failureReason != importResponse.Tuple.Failed[0].Reason {
t.Fatalf("expected: %s\nreturned: %s", failureReason, importResponse.Tuple.Failed[0].Reason)
}
})
}
Loading