diff --git a/cmd/tuple/delete.go b/cmd/tuple/delete.go index 3d08182d..3426d567 100644 --- a/cmd/tuple/delete.go +++ b/cmd/tuple/delete.go @@ -48,12 +48,12 @@ var deleteCmd = &cobra.Command{ if fileName != "" { - tuplesWithCondition, err := tuplefile.ReadTupleFile(fileName) + clientTuples, err := tuplefile.ReadTupleFile(fileName) if err != nil { return fmt.Errorf("failed to read file %s due to %w", fileName, err) } - var tuples = tuplefile.ClientTupleKeyToTupleKeyWithoutCondition(tuplesWithCondition) + var openfgaTuples = tuplefile.ClientTupleKeyToTupleKeyWithoutCondition(clientTuples) maxTuplesPerWrite, err := cmd.Flags().GetInt("max-tuples-per-write") if err != nil { return fmt.Errorf("failed to parse max tuples per write due to %w", err) @@ -65,7 +65,7 @@ var deleteCmd = &cobra.Command{ } deleteRequest := client.ClientWriteRequest{ - Deletes: tuples, + Deletes: openfgaTuples, } response, err := ImportTuples(fgaClient, deleteRequest, maxTuplesPerWrite, maxParallelRequests) if err != nil { diff --git a/cmd/tuple/delete_test.go b/cmd/tuple/delete_test.go new file mode 100644 index 00000000..fbba1644 --- /dev/null +++ b/cmd/tuple/delete_test.go @@ -0,0 +1,193 @@ +package tuple + +import ( + "testing" + + openfga "github.com/openfga/go-sdk" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/openfga/cli/internal/tuplefile" +) + +func TestDeleteTuplesFileData(t *testing.T) { //nolint:funlen + t.Parallel() + + tests := []struct { + name string + file string + expectedTuples []openfga.TupleKeyWithoutCondition + expectedError string + }{ + { + name: "it can correctly parse a csv file", + file: "testdata/tuples.csv", + expectedTuples: []openfga.TupleKeyWithoutCondition{ + { + User: "user:anne", + Relation: "owner", + Object: "folder:product", + }, + { + User: "folder:product", + Relation: "parent", + Object: "folder:product-2021", + }, + { + User: "team:fga#member", + Relation: "viewer", + Object: "folder:product-2021", + }, + }, + }, + { + name: "it can correctly parse a csv file regardless of columns order", + file: "testdata/tuples_other_columns_order.csv", + expectedTuples: []openfga.TupleKeyWithoutCondition{ + { + User: "user:anne", + Relation: "owner", + Object: "folder:product", + }, + { + User: "folder:product", + Relation: "parent", + Object: "folder:product-2021", + }, + { + User: "team:fga#member", + Relation: "viewer", + Object: "folder:product-2021", + }, + }, + }, + { + name: "it can correctly parse a csv file without optional fields", + file: "testdata/tuples_without_optional_fields.csv", + expectedTuples: []openfga.TupleKeyWithoutCondition{ + { + User: "user:anne", + Relation: "owner", + Object: "folder:product", + }, + { + User: "folder:product", + Relation: "parent", + Object: "folder:product-2021", + }, + }, + }, + { + name: "it can correctly parse a csv file with condition_name header but no condition_context header", + file: "testdata/tuples_with_condition_name_but_no_condition_context.csv", + expectedTuples: []openfga.TupleKeyWithoutCondition{ + { + User: "user:anne", + Relation: "owner", + Object: "folder:product", + }, + { + User: "folder:product", + Relation: "parent", + Object: "folder:product-2021", + }, + { + User: "team:fga#member", + Relation: "viewer", + Object: "folder:product-2021", + }, + }, + }, + { + name: "it can correctly parse a json file", + file: "testdata/tuples.json", + expectedTuples: []openfga.TupleKeyWithoutCondition{ + { + User: "user:anne", + Relation: "owner", + Object: "folder:product", + }, + { + User: "folder:product", + Relation: "parent", + Object: "folder:product-2021", + }, + { + User: "user:beth", + Relation: "viewer", + Object: "folder:product-2021", + }, + }, + }, + { + name: "it can correctly parse a yaml file", + file: "testdata/tuples.yaml", + expectedTuples: []openfga.TupleKeyWithoutCondition{ + { + User: "user:anne", + Relation: "owner", + Object: "folder:product", + }, + { + User: "folder:product", + Relation: "parent", + Object: "folder:product-2021", + }, + { + User: "user:beth", + Relation: "viewer", + Object: "folder:product-2021", + }, + }, + }, + { + name: "it fails to parse a non-supported file format", + file: "testdata/tuples.toml", + expectedError: "failed to parse input tuples: unsupported file format \".toml\"", + }, + { + name: "it fails to parse a csv file with wrong headers", + file: "testdata/tuples_wrong_headers.csv", + expectedError: "failed to parse input tuples: invalid header \"a\", valid headers are user_type,user_id,user_relation,relation,object_type,object_id,condition_name,condition_context", + }, + { + name: "it fails to parse a csv file with missing required headers", + file: "testdata/tuples_missing_required_headers.csv", + expectedError: "failed to parse input tuples: csv header missing (\"object_id\")", + }, + { + name: "it fails to parse a csv file with missing condition_name header when condition_context is present", + file: "testdata/tuples_missing_condition_name_header.csv", + expectedError: "failed to parse input tuples: missing \"condition_name\" header which is required when \"condition_context\" is present", + }, + { + name: "it fails to parse an empty csv file", + file: "testdata/tuples_empty.csv", + expectedError: "failed to parse input tuples: failed to read csv headers: EOF", + }, + { + name: "it fails to parse a csv file with invalid rows", + file: "testdata/tuples_with_invalid_rows.csv", + expectedError: "failed to parse input tuples: failed to read tuple from csv file: record on line 2: wrong number of fields", + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + actualTuples, err := tuplefile.ReadTupleFile(test.file) + deleteTuples := tuplefile.ClientTupleKeyToTupleKeyWithoutCondition(actualTuples) + + if test.expectedError != "" { + require.EqualError(t, err, test.expectedError) + + return + } + + require.NoError(t, err) + assert.Equal(t, test.expectedTuples, deleteTuples) + }) + } +} diff --git a/cmd/tuple/testdata/tuples.csv b/cmd/tuple/testdata/tuples.csv index 4e2a6210..ad40474a 100644 --- a/cmd/tuple/testdata/tuples.csv +++ b/cmd/tuple/testdata/tuples.csv @@ -1,4 +1,4 @@ user_type,user_id,user_relation,relation,object_type,object_id,condition_name,condition_context -user,chris,,owner,folder,product,inOfficeIP, +user,anne,,owner,folder,product,inOfficeIP, folder,product,,parent,folder,product-2021,inOfficeIP,"{""ip_addr"":""10.0.0.1""}" team,fga,member,viewer,folder,product-2021,, \ No newline at end of file