Skip to content

Commit

Permalink
feat(test): support tuple_file alongside of tuples (#212)
Browse files Browse the repository at this point in the history
  • Loading branch information
ewanharris authored Jan 5, 2024
2 parents 655a166 + 19281ff commit 40cdeaf
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 21 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ The tests file should be in yaml and have the following format:
---
name: Store Name # store name, optional
# model_file: ./model.fga # a global model that would apply to all tests, optional
# model can be used instead of model-file, optional
# model can be used instead of model_file, optional
model: |
model
schema 1.1
Expand All @@ -453,13 +453,15 @@ model: |
define can_write: owner or can_write from parent
define can_share: owner
# tuple_file: ./tuples.yaml # global tuples that would apply to all tests, optional
tuples: # global tuples that would apply to all tests, optional
- user: folder:1
relation: parent
object: folder:2
tests: # required
- name: test-1
description: testing that the model works # optional
# tuple_file: ./tuples.json # tuples that would apply per test
tuples:
- user: user:anne
relation: owner
Expand Down
17 changes: 17 additions & 0 deletions example/folder-document-access_tuples.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[
{
"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"
}
]
36 changes: 19 additions & 17 deletions example/model.fga.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,28 @@ model_file: ./model.fga # a global model that would apply to all tests
# schema 1.1
# type user
# ...
tuples: # global tuples that would apply to all tests
- user: folder:5
relation: parent
object: folder:product-2021
- user: folder:product-2021
relation: parent
object: folder:product-2021Q1
tuple_file: ./model_tuples.yaml # global tuples that would apply to all tests
# tuples can also be used instead of tuple_file
#tuples:
# - user: folder:5
# relation: parent
# object: folder:product-2021
# - user: folder:product-2021
# relation: parent
# object: folder:product-2021Q1
tests:
- name: "folder-document-access"
description: ""
tuples: # tuples in tests are appended to the global tuples and do not replace them
- 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
# tuples in tests are appended to the global tuples and do not replace them
tuple_file: ./folder-document-access_tuples.json
# tuples can also be used instead of tuple_file
#tuples:
# - user: folder:5
# relation: parent
# object: folder:product-2021
# - user: folder:product-2021
# relation: parent
# object: folder:product-2021Q1
check: # Each check test is made of: a user, an object and the expected result for one or more relations
- user: user:anne
object: folder:product-2021
Expand Down
6 changes: 6 additions & 0 deletions example/model_tuples.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- user: folder:5
relation: parent
object: folder:product-2021
- user: folder:product-2021
relation: parent
object: folder:product-2021Q1
9 changes: 7 additions & 2 deletions internal/storetest/localstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ func initLocalStore(
return &storeID, modelID, nil
}

func getLocalServerAndModel(
storeData StoreData,
func getLocalServerModelAndTuples(
storeData *StoreData,
basePath string,
) (*server.Server, *authorizationmodel.AuthzModel, error) {
var fgaServer *server.Server
Expand All @@ -83,6 +83,11 @@ func getLocalServerAndModel(
return nil, nil, err
}

err = storeData.LoadTuples(basePath)
if err != nil {
return nil, nil, err
}

if storeData.Model == "" {
return fgaServer, authModel, nil
}
Expand Down
80 changes: 80 additions & 0 deletions internal/storetest/storedata.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@ limitations under the License.
package storetest

import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path"

"github.com/openfga/go-sdk/client"

"gopkg.in/yaml.v3"

"github.com/openfga/cli/internal/authorizationmodel"
)

Expand All @@ -43,6 +50,7 @@ type ModelTest struct {
Name string `json:"name" yaml:"name"`
Description string `json:"description" yaml:"description"`
Tuples []client.ClientContextualTupleKey `json:"tuples" yaml:"tuples"`
TupleFile string `json:"tuple_file" yaml:"tuple_file"` //nolint:tagliatelle
Check []ModelTestCheck `json:"check" yaml:"check"`
ListObjects []ModelTestListObjects `json:"list_objects" yaml:"list_objects"` //nolint:tagliatelle
}
Expand All @@ -52,6 +60,7 @@ type StoreData struct {
Model string `json:"model" yaml:"model"`
ModelFile string `json:"model_file" yaml:"model_file"` //nolint:tagliatelle
Tuples []client.ClientContextualTupleKey `json:"tuples" yaml:"tuples"`
TupleFile string `json:"tuple_file" yaml:"tuple_file"` //nolint:tagliatelle
Tests []ModelTest `json:"tests" yaml:"tests"`
}

Expand Down Expand Up @@ -82,3 +91,74 @@ func (storeData *StoreData) LoadModel(basePath string) (authorizationmodel.Model

return format, nil
}

func (storeData *StoreData) LoadTuples(basePath string) error {
var errs error

if storeData.TupleFile != "" {
tuples, err := readTupleFile(path.Join(basePath, storeData.TupleFile))
if err != nil {
errs = fmt.Errorf("failed to process global tuple %s file due to %w", storeData.TupleFile, err)
} else {
storeData.Tuples = tuples
}
}

for index := 0; index < len(storeData.Tests); index++ {
test := storeData.Tests[index]
if test.TupleFile == "" {
continue
}

tuples, err := readTupleFile(path.Join(basePath, test.TupleFile))
if err != nil {
errs = errors.Join(
errs,
fmt.Errorf("failed to process tuple file %s for test %s due to %w", test.TupleFile, test.Name, err),
)
} else {
storeData.Tests[index].Tuples = tuples
}
}

if errs != nil {
return errors.Join(errors.New("failed to process one or more tuple files"), errs) //nolint:goerr113
}

return nil
}

func readTupleFile(tuplePath string) ([]client.ClientContextualTupleKey, error) {
var tuples []client.ClientContextualTupleKey

tupleFile, err := os.Open(tuplePath)
if err != nil {
return nil, err //nolint:wrapcheck
}
defer tupleFile.Close()

switch path.Ext(tuplePath) {
case ".json":
contents, err := io.ReadAll(tupleFile)
if err != nil {
return nil, err //nolint:wrapcheck
}

err = json.Unmarshal(contents, &tuples)
if err != nil {
return nil, err //nolint:wrapcheck
}
case ".yaml", ".yml":
decoder := yaml.NewDecoder(tupleFile)
decoder.KnownFields(true)

err = decoder.Decode(&tuples)
if err != nil {
return nil, err //nolint:wrapcheck
}
default:
return nil, fmt.Errorf("unsupported file format %s", path.Ext(tuplePath)) //nolint:goerr113
}

return tuples, nil
}
2 changes: 1 addition & 1 deletion internal/storetest/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func RunTests(
) (TestResults, error) {
test := TestResults{}

fgaServer, authModel, err := getLocalServerAndModel(storeData, basePath)
fgaServer, authModel, err := getLocalServerModelAndTuples(&storeData, basePath)
if err != nil {
return test, err
}
Expand Down

0 comments on commit 40cdeaf

Please sign in to comment.