diff --git a/pkg/test/step.go b/pkg/test/step.go index 2c0b605d..89d33c3b 100644 --- a/pkg/test/step.go +++ b/pkg/test/step.go @@ -4,12 +4,15 @@ import ( "context" "errors" "fmt" + "os" "path/filepath" "regexp" "strings" "testing" "time" + "gopkg.in/yaml.v2" + k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" @@ -561,6 +564,11 @@ func (s *Step) LoadYAML(file string) error { for _, apply := range s.Apply { if apply.object.GetObjectKind().GroupVersionKind().Kind == "TestStep" { if testStep, ok := apply.object.(*harness.TestStep); ok { + // validate yaml files and teststep fields for TestStep objects + err := testStepYamlValidate(testStep, s.Dir) + if err != nil { + return fmt.Errorf("failed to validate TestStep object from %s, error: %v", file, err) + } if s.Step != nil { return fmt.Errorf("more than 1 TestStep not allowed in step %q", s.Name) } @@ -688,3 +696,71 @@ func hasTimeoutErr(err []error) bool { } return false } + +func testStepYamlValidate(ts *harness.TestStep, dir string) error { + // If apply field is not present, then either command or delete needs to be present, + // if both are not specified it should throw an error + if len(ts.Apply) == 0 && len(ts.Commands) == 0 && len(ts.Delete) == 0 { + return fmt.Errorf("if apply field is not specified either command or delete is expected") + } + + // Assert and Error fields require Apply field + if (len(ts.Assert) != 0 || len(ts.Error) != 0) && len(ts.Apply) == 0 { + return fmt.Errorf("cannot have assert or error field when missing apply field") + } + + // Validation for referenced field files + for _, Apply := range ts.Apply { + err := validateFieldFile(Apply.File, dir) + if err != nil { + return errors.Join(err, fmt.Errorf("error in apply field")) + } + } + for _, Assert := range ts.Assert { + err := validateFieldFile(Assert, dir) + if err != nil { + return errors.Join(err, fmt.Errorf("error in assert field")) + } + } + for _, Error := range ts.Error { + err := validateFieldFile(Error, dir) + if err != nil { + return errors.Join(err, fmt.Errorf("error in error field")) + } + } + + return nil +} + +// validateFieldFile checks if the reference file exists +func validateFieldFile(file string, dir string) error { + completeFile := filepath.Join(dir, file) + _, err := os.Stat(completeFile) + if os.IsNotExist(err) { + return fmt.Errorf("the file %s specified in the field does not exist", completeFile) + } + + // Check if the file is in yaml format + err = validateYAMLFormat(completeFile) + if err != nil { + return fmt.Errorf("unable to validate yaml format and indentation for file %s", completeFile) + } + + return nil +} + +// validateYAMLFormat checks if the yaml file is in correct format and indentation +func validateYAMLFormat(file string) error { + data, err := os.ReadFile(file) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", file, err) + } + + var temp []client.Object + err = yaml.Unmarshal(data, &temp) + if err != nil { + return fmt.Errorf("failed to parse yaml file %s: %w", file, err) + } + + return nil +}