ops/ops.go (169 lines of code) (raw):

package ops import ( "encoding/json" "fmt" "reflect" "strings" "github.com/Azure/terratest-terraform-fluent/testerror" "github.com/stretchr/testify/assert" "github.com/tidwall/gjson" ) // JsonAssertionFunc is a function which can be used to unmarshal a raw JSON message and check its contents. type JsonAssertionFunc func(input json.RawMessage) (*bool, error) // Operative is a type which can be used to compare the expected and actual values of a given combination type Operative struct { Reference string Actual any Exist bool err *testerror.Error } // Exists returns a non-nil *testerror.Error if the resource does not exist in the plan or if the key does not exist in the resource func (o Operative) Exists() *testerror.Error { if err := isErrorOrNotExist(o); err != nil { return err } return nil } // DoesNotExist returns a non-nil *testerror.Error if the resource does not exist in the plan or if the key exists in the resource func (o Operative) DoesNotExist() *testerror.Error { if o.Exist { return testerror.Newf( "%s: found when not expected", o.Reference, ) } return nil } // HasValue returns a non-nil *testerror.Error if the resource does not exist in the plan // or if the value of the key does not match the expected value func (o Operative) HasValue(expected any) *testerror.Error { if err := isErrorOrNotExist(o); err != nil { return err } if err := validateEqualArgs(expected, o.Actual); err != nil { return testerror.Newf("invalid operation: %#v == %#v (%s)", expected, o.Actual, err, ) } if !assert.ObjectsAreEqualValues(expected, o.Actual) { return testerror.Newf( "%s: expected value %v not equal to actual %v", o.Reference, expected, o.Actual, ) } return nil } // ContainsString returns a non-nil *testerror.Error if the resource does not exist in the plan or if // the value of the key does not contain the expected string func (o Operative) ContainsString(expected string) *testerror.Error { if err := isErrorOrNotExist(o); err != nil { return err } actualString, ok := o.Actual.(string) if !ok { return testerror.Newf("Cannot convert value to string: %s", o.Reference) } if !strings.Contains(actualString, expected) { return testerror.Newf( "%s: expected value %s not contained within %s", o.Reference, expected, actualString, ) } return nil } // GetValue returns the actual value and a *testerror.Error func (o Operative) GetValue() (any, error) { if err := isErrorOrNotExist(o); err != nil { return nil, err } return o.Actual, nil } // ContainsJsonValue returns a *testerror.Error which asserts upon a given JSON string set // by deserializing it and then asserting on it via the JsonAssertionFunc. func (o Operative) ContainsJsonValue(assertion JsonAssertionFunc) *testerror.Error { if err := isErrorOrNotExist(o); err != nil { return err } if o.Actual == nil || o.Actual == "" { return testerror.Newf( "%s: is empty", o.Reference, ) } actual, actualok := o.Actual.(string) if !actualok { return testerror.Newf( "%s: value is not a string", o.Reference, ) } j := json.RawMessage(actual) assertok, err := assertion(j) if err != nil { return testerror.Newf( "%s: asserting value for %q: %+v", o.Reference, o.Actual, err, ) } if assertok == nil || !*assertok { return testerror.Newf( "%s: assertion failed for %q", o.Reference, o.Actual, ) } return nil } // Query executes the provided gjson query on the data in the actual value // and overwrites the actual value with the result of the query. // https://github.com/tidwall/gjson func (o Operative) Query(query string) Operative { if err := isErrorOrNotExist(o); err != nil { o.Exist = false o.err = err return o } o.Reference = fmt.Sprintf("%s?%s", o.Reference, query) var bytes []byte // If the actual value is a string, we assume it is JSON and try to parse it. // Otherwise, we marshal it to JSON and try to parse it. actual, ok := o.Actual.(string) if ok { bytes = []byte(actual) } else { bytes, _ = json.Marshal(o.Actual) } if !gjson.ValidBytes(bytes) { o.err = testerror.Newf( "%s: actual value %s not valid JSON", o.Reference, o.Actual, ) o.Actual = nil return o } o.Actual = gjson.GetBytes(bytes, query).Value() if o.Actual == nil { o.Exist = false } return o } // validateEqualArgs checks whether provided arguments can be safely used in the // HasValue function. func validateEqualArgs(expected, actual any) error { if expected == nil && actual == nil { return nil } if isFunction(expected) || isFunction(actual) { return fmt.Errorf("cannot take func type as argument") } return nil } func isFunction(arg any) bool { if arg == nil { return false } return reflect.TypeOf(arg).Kind() == reflect.Func } func isErrorOrNotExist(o Operative) *testerror.Error { if o.err != nil { return o.err } if !o.Exist { return testerror.Newf( "%s: not found when expected", o.Reference, ) } return nil }