@@ -25,9 +25,15 @@ import (
2525const (
2626 ResourceSeparator = ":"
2727 RelationSeparator = "#"
28+
29+ TestFileExtensionJSON = ".test.json"
30+ TestFileExtensionYAML = ".test.yaml"
31+ TestFileExtensionYML = ".test.yml"
2832)
2933
3034var resourceTypesFile string
35+ var AcceptedTestFileExtensions = []string {TestFileExtensionJSON , TestFileExtensionYAML , TestFileExtensionYML }
36+ var AcceptedTestFileExtensionErrorMessage = fmt .Sprintf ("Accepted test file extensions are: %s" , strings .Join (AcceptedTestFileExtensions , ", " ))
3137
3238func init () {
3339 // resource-types
@@ -810,55 +816,66 @@ var applySchemaCmd = &cobra.Command{
810816}
811817
812818// Test spec types
813- type warrants struct {
819+
820+ type WarrantSetup struct {
814821 Subject string `yaml:"subject" json:"subject"`
815822 Relation string `yaml:"relation" json:"relation"`
816823 Resource string `yaml:"resource" json:"resource"`
817824 Policy string `yaml:"policy,omitempty" json:"policy,omitempty"`
818825}
819826
820- type setupSection struct {
821- Warrants []warrants `yaml:"warrants" json:"warrants"`
827+ type FGATestSetupSection struct {
828+ Warrants []WarrantSetup `yaml:"warrants" json:"warrants"`
822829}
823830
824- type testCheck struct {
831+ type FGATestCheck struct {
825832 Subject string `yaml:"subject" json:"subject"`
826833 Relation string `yaml:"relation" json:"relation"`
827834 Resource string `yaml:"resource" json:"resource"`
828835 Context map [string ]interface {} `yaml:"context,omitempty" json:"context,omitempty"`
829836}
830837
831- type testCase struct {
832- Name string `yaml:"name" json:"name"`
833- Check testCheck `yaml:"check" json:"check"`
834- Expect bool `yaml:"expect" json:"expect"`
838+ type FGATestCase struct {
839+ Name string `yaml:"name" json:"name"`
840+ Check FGATestCheck `yaml:"check" json:"check"`
841+ Expect bool `yaml:"expect" json:"expect"`
842+ }
843+
844+ type FGATestTeardownSection struct {
845+ Warrants []WarrantSetup `yaml:"warrants" json:"warrants"`
846+ Resources []string `yaml:"resources" json:"resources"`
835847}
836848
837- type teardownSection struct {
838- Warrants []warrants `yaml:"warrants" json:"warrants"`
839- Resources []string `yaml:"resources" json:"resources"`
849+ type FGATestYaml struct {
850+ Setup FGATestSetupSection `yaml:"setup" json:"setup"`
851+ Tests []FGATestCase `yaml:"tests" json:"tests"`
852+ Teardown * FGATestTeardownSection `yaml:"teardown,omitempty" json:"teardown,omitempty"`
840853}
841854
842- type testYaml struct {
843- Setup setupSection `yaml:"setup" json:"setup"`
844- Tests [] testCase `yaml:"tests" json:"tests"`
845- Teardown * teardownSection `yaml:"teardown,omitempty" json:"teardown,omitempty"`
855+ type FGATestResult struct {
856+ Passed bool
857+ NumPassed int
858+ NumTotal int
846859}
847860
848- func parseTestFile (testFile string ) (testYaml , error ) {
849- var testSpec testYaml
861+ func parseTestFile (testFile string ) (FGATestYaml , error ) {
862+ var testSpec FGATestYaml
850863 bytes , err := os .ReadFile (testFile )
851864 if err != nil {
852865 return testSpec , errors .Wrap (err , "failed to read test file" )
853866 }
854- ext := filepath .Ext (testFile )
855- switch ext {
856- case ".yaml" , ".yml" :
867+
868+ if ! isTestFile (testFile ) {
869+ return testSpec , errors .Errorf ("invalid test file format. %s" , AcceptedTestFileExtensionErrorMessage )
870+ }
871+
872+ switch {
873+ case strings .HasSuffix (testFile , TestFileExtensionYAML ), strings .HasSuffix (testFile , TestFileExtensionYML ):
857874 err = yaml .Unmarshal (bytes , & testSpec )
858- case ".json" :
875+ case strings . HasSuffix ( testFile , TestFileExtensionJSON ) :
859876 err = json .Unmarshal (bytes , & testSpec )
860877 default :
861- return testSpec , errors .Errorf ( "unsupported test file extension: %s (must be .yaml, .yml, or .json)" , ext )
878+ return testSpec , errors .New ( "failed to parse test file: " + AcceptedTestFileExtensionErrorMessage )
862879 }
863880 if err != nil {
864881 return testSpec , errors .Wrap (err , "failed to parse test file" )
@@ -874,7 +891,7 @@ func parseTestFile(testFile string) (testYaml, error) {
874891 return testSpec , nil
875892}
876893
877- func setupWarrants (setup setupSection ) (string , error ) {
894+ func setupTestWarrants (setup FGATestSetupSection ) (string , error ) {
878895 batchWrites := make ([]fga.WriteWarrantOpts , 0 , len (setup .Warrants ))
879896 for _ , w := range setup .Warrants {
880897 subjectType , subjectIdRelation , valid := strings .Cut (w .Subject , ResourceSeparator )
@@ -909,13 +926,7 @@ func setupWarrants(setup setupSection) (string, error) {
909926 return "" , nil
910927}
911928
912- type TestResult struct {
913- Passed bool
914- NumPassed int
915- NumTotal int
916- }
917-
918- func runTestCases (tests []testCase , warrantToken string ) (TestResult , error ) {
929+ func runTestCases (tests []FGATestCase , warrantToken string ) (FGATestResult , error ) {
919930 allPassed := true
920931 passed := 0
921932 total := len (tests )
@@ -964,10 +975,10 @@ func runTestCases(tests []testCase, warrantToken string) (TestResult, error) {
964975 allPassed = false
965976 }
966977 }
967- return TestResult {Passed : allPassed , NumPassed : passed , NumTotal : total }, nil
978+ return FGATestResult {Passed : allPassed , NumPassed : passed , NumTotal : total }, nil
968979}
969980
970- func teardown (teardown * teardownSection ) error {
981+ func teardownTest (teardown * FGATestTeardownSection ) error {
971982 if teardown == nil {
972983 return nil
973984 }
@@ -1018,7 +1029,7 @@ func teardown(teardown *teardownSection) error {
10181029 return nil
10191030}
10201031
1021- func cleanupResources (cmd * cobra.Command , setup setupSection ) error {
1032+ func cleanupTestResources (cmd * cobra.Command , setup FGATestSetupSection ) error {
10221033 cleanupResources , err := cmd .Flags ().GetBool ("cleanup" )
10231034 if err != nil {
10241035 return errors .Wrap (err , "failed to get cleanup flag" )
@@ -1047,62 +1058,76 @@ func cleanupResources(cmd *cobra.Command, setup setupSection) error {
10471058 return nil
10481059}
10491060
1050- func runTestFile (cmd * cobra.Command , testFile string ) (TestResult , error ) {
1061+ func runTestFile (cmd * cobra.Command , testFile string ) (FGATestResult , error ) {
10511062 testSpec , err := parseTestFile (testFile )
10521063 if err != nil {
1053- return TestResult {}, err
1064+ return FGATestResult {}, err
10541065 }
1055- warrantToken , err := setupWarrants (testSpec .Setup )
1066+ warrantToken , err := setupTestWarrants (testSpec .Setup )
10561067 if err != nil {
10571068 printer .PrintMsg (printer .RedText (printer .Cross , err .Error ()))
1058- return TestResult {}, err
1069+ return FGATestResult {}, err
10591070 }
10601071 result , err := runTestCases (testSpec .Tests , warrantToken )
10611072 if err != nil {
10621073 return result , err
10631074 }
1064- if err := teardown (testSpec .Teardown ); err != nil {
1075+ if err := teardownTest (testSpec .Teardown ); err != nil {
10651076 return result , err
10661077 }
1067- if err := cleanupResources (cmd , testSpec .Setup ); err != nil {
1078+ if err := cleanupTestResources (cmd , testSpec .Setup ); err != nil {
10681079 return result , err
10691080 }
10701081 return result , nil
10711082}
10721083
1073- func runTestDirectory (cmd * cobra.Command , path string ) (TestResult , error ) {
1084+ func isTestFile (fileName string ) bool {
1085+ for _ , extToCheck := range AcceptedTestFileExtensions {
1086+ if strings .HasSuffix (fileName , extToCheck ) {
1087+ return true
1088+ }
1089+ }
1090+
1091+ return false
1092+ }
1093+
1094+ func runTestDirectory (cmd * cobra.Command , path string ) (FGATestResult , error ) {
10741095 entries , err := os .ReadDir (path )
10751096 if err != nil {
1076- return TestResult {}, errors .Wrap (err , "failed to read directory" )
1097+ return FGATestResult {}, errors .Wrap (err , "failed to read directory" )
10771098 }
10781099 var testFiles []string
10791100 for _ , entry := range entries {
10801101 if entry .IsDir () {
10811102 continue
10821103 }
10831104 name := entry .Name ()
1084- if strings . HasSuffix (name , ".test.yaml" ) || strings . HasSuffix ( name , ".test.yml" ) || strings . HasSuffix ( name , ".test.json" ) {
1105+ if isTestFile (name ) {
10851106 testFiles = append (testFiles , filepath .Join (path , name ))
10861107 }
10871108 }
10881109 if len (testFiles ) == 0 {
1089- printer .PrintMsg ("No test files found in directory. Make sure to name your test files with the .test.yaml, .test.yml, or .test.json extension." )
1090- return TestResult {Passed : true , NumPassed : 0 , NumTotal : 0 }, nil
1110+ printer .PrintMsg ("No test files found in directory. " + AcceptedTestFileExtensionErrorMessage )
1111+ return FGATestResult {Passed : true , NumPassed : 0 , NumTotal : 0 }, nil
10911112 }
1092- finalResult := TestResult {Passed : true , NumPassed : 0 , NumTotal : 0 }
1113+
1114+ finalResult := FGATestResult {Passed : true , NumPassed : 0 , NumTotal : 0 }
10931115 for _ , file := range testFiles {
10941116 printer .PrintMsg ("" )
10951117 printer .PrintMsg ("Running tests in " + file )
10961118 result , err := runTestFile (cmd , file )
1119+
10971120 finalResult .NumPassed += result .NumPassed
10981121 finalResult .NumTotal += result .NumTotal
1122+
10991123 if err != nil {
11001124 printer .PrintMsg (printer .RedText (printer .Cross , err .Error ()))
11011125 finalResult .Passed = false
11021126 } else if ! result .Passed {
11031127 finalResult .Passed = false
11041128 }
11051129 }
1130+
11061131 printer .PrintMsg ("" )
11071132 if finalResult .Passed {
11081133 printer .PrintMsg (fmt .Sprintf ("All tests passed (%d/%d)" , finalResult .NumPassed , finalResult .NumTotal ))
@@ -1114,9 +1139,9 @@ func runTestDirectory(cmd *cobra.Command, path string) (TestResult, error) {
11141139}
11151140
11161141var testCmd = & cobra.Command {
1117- Use : "test <test_file.yaml|json|directory>" ,
1118- Short : "Run FGA permission tests from a YAML/JSON file or all test files in a directory" ,
1119- Long : `Run FGA permission tests from a YAML or JSON file, or all .test.yaml/.test.json files in a directory, setting up and tearing down warrants and resources as specified.
1142+ Use : "test <test_file.test .yaml|json|directory>" ,
1143+ Short : "Run FGA permission tests from a YAML/JSON file or all test files in a directory" ,
1144+ Long : `Run FGA permission tests from a YAML or JSON file, or all .test.yaml/.test.json files in a directory, setting up and tearing down warrants and resources as specified.
11201145
11211146Test file format (YAML or JSON):
11221147
@@ -1160,7 +1185,7 @@ JSON:
11601185
11611186Each test file must have a 'setup' section, a 'tests' array, and an optional 'teardown' section.
11621187` ,
1163- Example : "workos fga test ./tests. yaml --cleanup\n workos fga test ./tests .json --cleanup\n workos fga test ./test-directory/ --cleanup" ,
1188+ Example : "workos fga test ./schema.test. yaml --cleanup\n workos fga test ./schema.test .json --cleanup\n workos fga test ./test-directory/ --cleanup" ,
11641189 Args : cobra .ExactArgs (1 ),
11651190 RunE : func (cmd * cobra.Command , args []string ) error {
11661191 path := args [0 ]
0 commit comments