Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: display success/failed tuples import #319

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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 @@
"github.com/openfga/cli/internal/storetest"
)

// importStoreIODependencies defines IO dependencies for importing store

Check failure on line 36 in cmd/store/import.go

View workflow job for this annotation

GitHub Actions / Lints

Comment should end in a period (godot)
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

Check failure on line 77 in cmd/store/import.go

View workflow job for this annotation

GitHub Actions / Lints

`if storeID == ""` has complex nested blocks (complexity: 5) (nestif)
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 @@
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 @@
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 @@
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 @@
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"

Check failure on line 4 in cmd/store/import_test.go

View workflow job for this annotation

GitHub Actions / Lints

File is not `gofumpt`-ed (gofumpt)
"github.com/openfga/cli/cmd/tuple"

Check failure on line 5 in cmd/store/import_test.go

View workflow job for this annotation

GitHub Actions / Lints

File is not `goimports`-ed with -local github.com/openfga/cli (goimports)
"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"

Check failure on line 10 in cmd/store/import_test.go

View workflow job for this annotation

GitHub Actions / Lints

File is not `gofumpt`-ed (gofumpt)
"reflect"
"testing"
"time"
)

func TestImportStore(t *testing.T) {

Check failure on line 16 in cmd/store/import_test.go

View workflow job for this annotation

GitHub Actions / Lints

Function 'TestImportStore' is too long (189 > 120) (funlen)
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) {

Check failure on line 82 in cmd/store/import_test.go

View workflow job for this annotation

GitHub Actions / Lints

line is 129 characters (lll)
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
Loading