Merge pull request #139 from lburgazzoli/matchers
Use github.com/lburgazzoli/gomega-matchers
This commit is contained in:
commit
734b4bf507
3
go.mod
3
go.mod
|
@ -1,6 +1,6 @@
|
|||
module github.com/dapr-sandbox/dapr-kubernetes-operator
|
||||
|
||||
go 1.22
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v1.4.1
|
||||
|
@ -90,6 +90,7 @@ require (
|
|||
github.com/klauspost/compress v1.16.0 // indirect
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/lburgazzoli/gomega-matchers v0.0.0-20240423093716-45891b1edc66 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -247,6 +247,10 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq
|
|||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||
github.com/lburgazzoli/gomega-matchers v0.0.0-20240423084142-42952d04d67a h1:qil/O2L2zJlmBzjutJ4XObs26fbjE8x/XbyI4afX9yA=
|
||||
github.com/lburgazzoli/gomega-matchers v0.0.0-20240423084142-42952d04d67a/go.mod h1:wKad323qq77Y2PU1kwLcHx/uO3l8A1m5/fiCk8MFreE=
|
||||
github.com/lburgazzoli/gomega-matchers v0.0.0-20240423093716-45891b1edc66 h1:KNUCtO+GgZ72eG9rS1dzINhmrz7a5xQ7iuuQSeTwJU0=
|
||||
github.com/lburgazzoli/gomega-matchers v0.0.0-20240423093716-45891b1edc66/go.mod h1:wKad323qq77Y2PU1kwLcHx/uO3l8A1m5/fiCk8MFreE=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
|
@ -9,7 +10,7 @@ import (
|
|||
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support"
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/dapr"
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/matchers"
|
||||
"github.com/lburgazzoli/gomega-matchers/pkg/matchers/jq"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
@ -31,7 +32,7 @@ func ValidateDaprApp(test support.Test, namespace string) {
|
|||
"Failure checking for App Deployment",
|
||||
)
|
||||
test.Eventually(support.PodList(test, "app="+appName, namespace), support.TestTimeoutShort).Should(
|
||||
gomega.WithTransform(matchers.AsJSON(), matchers.MatchJQ(".items[0].spec.containers | length == 2")),
|
||||
gomega.WithTransform(json.Marshal, jq.Match(".items[0].spec.containers | length == 2")),
|
||||
"Failure checking for App Pods (sidecar injection)",
|
||||
)
|
||||
|
||||
|
@ -58,7 +59,7 @@ func ValidateDaprApp(test support.Test, namespace string) {
|
|||
test.Eventually(dapr.GET(test, base+"/read"), support.TestTimeoutLong).Should(
|
||||
gomega.And(
|
||||
gomega.HaveHTTPStatus(http.StatusOK),
|
||||
gomega.HaveHTTPBody(gomega.Not(matchers.MatchJQf(`.Values | any(. == "%s")`, value))),
|
||||
gomega.HaveHTTPBody(gomega.Not(jq.Match(`.Values | any(. == "%s")`, value))),
|
||||
),
|
||||
"Failure to read initial values",
|
||||
)
|
||||
|
@ -73,7 +74,7 @@ func ValidateDaprApp(test support.Test, namespace string) {
|
|||
test.Eventually(dapr.GET(test, base+"/read"), support.TestTimeoutLong).Should(
|
||||
gomega.And(
|
||||
gomega.HaveHTTPStatus(http.StatusOK),
|
||||
gomega.HaveHTTPBody(matchers.MatchJQf(`.Values | any(. == "%s")`, value)),
|
||||
gomega.HaveHTTPBody(jq.Match(`.Values | any(. == "%s")`, value)),
|
||||
),
|
||||
"Failure to read final values",
|
||||
)
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package operator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/lburgazzoli/gomega-matchers/pkg/matchers/jq"
|
||||
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/conditions"
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/controller"
|
||||
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/dapr"
|
||||
|
@ -13,7 +16,6 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
. "github.com/dapr-sandbox/dapr-kubernetes-operator/test/support"
|
||||
. "github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/matchers"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
daprAc "github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/client/operator/applyconfiguration/operator/v1alpha1"
|
||||
|
@ -43,10 +45,10 @@ func TestDaprInstanceDeployWithDefaults(t *testing.T) {
|
|||
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
|
||||
|
||||
test.Eventually(dapr.Instance(test, instance), TestTimeoutLong).Should(
|
||||
WithTransform(AsJSON(), And(
|
||||
MatchJQ(`.status.chart.name == "dapr"`),
|
||||
MatchJQ(`.status.chart.repo == "embedded"`),
|
||||
MatchJQ(`.status.chart.version == "1.13.2"`),
|
||||
WithTransform(json.Marshal, And(
|
||||
jq.Match(`.status.chart.name == "dapr"`),
|
||||
jq.Match(`.status.chart.repo == "embedded"`),
|
||||
jq.Match(`.status.chart.version == "1.13.2"`),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
@ -76,10 +78,10 @@ func TestDaprInstanceDeployWithCustomChart(t *testing.T) {
|
|||
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
|
||||
|
||||
test.Eventually(dapr.Instance(test, instance), TestTimeoutLong).Should(
|
||||
WithTransform(AsJSON(), And(
|
||||
MatchJQ(`.status.chart.name == "dapr"`),
|
||||
MatchJQ(`.status.chart.repo == "https://dapr.github.io/helm-charts"`),
|
||||
MatchJQ(`.status.chart.version == "1.13.0"`),
|
||||
WithTransform(json.Marshal, And(
|
||||
jq.Match(`.status.chart.name == "dapr"`),
|
||||
jq.Match(`.status.chart.repo == "https://dapr.github.io/helm-charts"`),
|
||||
jq.Match(`.status.chart.version == "1.13.0"`),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
@ -113,17 +115,17 @@ func TestDaprInstanceDeployWithCustomSidecarImage(t *testing.T) {
|
|||
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
|
||||
|
||||
test.Eventually(dapr.Instance(test, instance), TestTimeoutLong).Should(
|
||||
WithTransform(AsJSON(), And(
|
||||
MatchJQ(`.status.chart.name == "dapr"`),
|
||||
MatchJQ(`.status.chart.repo == "embedded"`),
|
||||
MatchJQ(`.status.chart.version == "1.13.2"`),
|
||||
WithTransform(json.Marshal, And(
|
||||
jq.Match(`.status.chart.name == "dapr"`),
|
||||
jq.Match(`.status.chart.repo == "embedded"`),
|
||||
jq.Match(`.status.chart.version == "1.13.2"`),
|
||||
)),
|
||||
)
|
||||
|
||||
test.Eventually(PodList(test, "app=dapr-sidecar-injector", instance.Namespace), TestTimeoutLong).Should(
|
||||
WithTransform(AsJSON(), And(
|
||||
MatchJQf(`.items[0].spec.containers[0].env[] | select(.name == "SIDECAR_IMAGE") | .value == "docker.io/daprio/daprd:%s"`, test.ID()),
|
||||
MatchJQf(`.items[0].spec.containers[0].env[] | select(.name == "SIDECAR_IMAGE_PULL_POLICY") | .value == "%s"`, corev1.PullAlways),
|
||||
WithTransform(json.Marshal, And(
|
||||
jq.Match(`.items[0].spec.containers[0].env[] | select(.name == "SIDECAR_IMAGE") | .value == "docker.io/daprio/daprd:%s"`, test.ID()),
|
||||
jq.Match(`.items[0].spec.containers[0].env[] | select(.name == "SIDECAR_IMAGE_PULL_POLICY") | .value == "%s"`, corev1.PullAlways),
|
||||
)),
|
||||
)
|
||||
|
||||
|
@ -159,10 +161,10 @@ func TestDaprInstanceDeployWithApp(t *testing.T) {
|
|||
WithTransform(ConditionStatus(appsv1.DeploymentAvailable), Equal(corev1.ConditionTrue)))
|
||||
|
||||
test.Eventually(dapr.Instance(test, instance), TestTimeoutLong).Should(
|
||||
WithTransform(AsJSON(), And(
|
||||
MatchJQ(`.status.chart.name == "dapr"`),
|
||||
MatchJQ(`.status.chart.repo == "embedded"`),
|
||||
MatchJQ(`.status.chart.version == "1.13.2"`),
|
||||
WithTransform(json.Marshal, And(
|
||||
jq.Match(`.status.chart.name == "dapr"`),
|
||||
jq.Match(`.status.chart.repo == "embedded"`),
|
||||
jq.Match(`.status.chart.version == "1.13.2"`),
|
||||
)),
|
||||
)
|
||||
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
package matchers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/itchyny/gojq"
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/types"
|
||||
)
|
||||
|
||||
func MatchJQ(expression string) types.GomegaMatcher {
|
||||
return &jqMatcher{
|
||||
Expression: expression,
|
||||
}
|
||||
}
|
||||
|
||||
func MatchJQf(format string, args ...any) types.GomegaMatcher {
|
||||
return &jqMatcher{
|
||||
Expression: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
var _ types.GomegaMatcher = &jqMatcher{}
|
||||
|
||||
type jqMatcher struct {
|
||||
Expression string
|
||||
firstFailurePath []interface{}
|
||||
}
|
||||
|
||||
func (matcher *jqMatcher) Match(actual interface{}) (bool, error) {
|
||||
query, err := gojq.Parse(matcher.Expression)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
actualString, ok := toString(actual)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("MatchJQMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
data := make(map[string]interface{})
|
||||
if err := json.Unmarshal([]byte(actualString), &data); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
it := query.Run(data)
|
||||
|
||||
v, ok := it.Next()
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err, ok := v.(error); ok {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if match, ok := v.(bool); ok {
|
||||
return match, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (matcher *jqMatcher) FailureMessage(actual interface{}) string {
|
||||
actualString, expectedString, _ := matcher.prettyPrint(actual)
|
||||
return formattedMessage(format.Message(actualString, "to match JQ Expression", expectedString), matcher.firstFailurePath)
|
||||
}
|
||||
|
||||
func (matcher *jqMatcher) NegatedFailureMessage(actual interface{}) string {
|
||||
actualString, expectedString, _ := matcher.prettyPrint(actual)
|
||||
return formattedMessage(format.Message(actualString, "not to match JQ expression", expectedString), matcher.firstFailurePath)
|
||||
}
|
||||
|
||||
func (matcher *jqMatcher) prettyPrint(actual interface{}) (string, string, error) {
|
||||
actualString, ok := toString(actual)
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("the MatchJQMatcher matcher requires a string, stringer, or []byte. Got actual:\n%s", format.Object(actual, 1))
|
||||
}
|
||||
|
||||
abuf := new(bytes.Buffer)
|
||||
|
||||
if err := json.Indent(abuf, []byte(actualString), "", " "); err != nil {
|
||||
return "", "", fmt.Errorf("actual '%s' should match '%s', but it does not.\nUnderlying error: %w", actualString, matcher.Expression, err)
|
||||
}
|
||||
|
||||
return abuf.String(), matcher.Expression, nil
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package matchers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestJQMatcher(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
g.Expect(`{"a":1}`).Should(MatchJQ(`.a == 1`))
|
||||
g.Expect(`{"a":1}`).Should(Not(MatchJQ(`.a == 2`)))
|
||||
g.Expect(`{"Values":[ "foo" ]}`).Should(MatchJQ(`.Values | if . then any(. == "foo") else false end`))
|
||||
g.Expect(`{"Values":[ "foo" ]}`).Should(Not(MatchJQ(`.Values | if . then any(. == "bar") else false end`)))
|
||||
g.Expect(`{"Values": null}`).Should(Not(MatchJQ(`.Values | if . then any(. == "foo") else false end`)))
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package matchers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func formattedMessage(comparisonMessage string, failurePath []interface{}) string {
|
||||
var diffMessage string
|
||||
if len(failurePath) == 0 {
|
||||
diffMessage = ""
|
||||
} else {
|
||||
diffMessage = "\n\nfirst mismatched key: " + formattedFailurePath(failurePath)
|
||||
}
|
||||
return fmt.Sprintf("%s%s", comparisonMessage, diffMessage)
|
||||
}
|
||||
|
||||
func formattedFailurePath(failurePath []interface{}) string {
|
||||
formattedPaths := []string{}
|
||||
for i := len(failurePath) - 1; i >= 0; i-- {
|
||||
switch p := failurePath[i].(type) {
|
||||
case int:
|
||||
formattedPaths = append(formattedPaths, fmt.Sprintf(`[%d]`, p))
|
||||
default:
|
||||
if i != len(failurePath)-1 {
|
||||
formattedPaths = append(formattedPaths, ".")
|
||||
}
|
||||
formattedPaths = append(formattedPaths, fmt.Sprintf(`"%s"`, p))
|
||||
}
|
||||
}
|
||||
return strings.Join(formattedPaths, "")
|
||||
}
|
||||
|
||||
func toString(a interface{}) (string, bool) {
|
||||
aString, isString := a.(string)
|
||||
if isString {
|
||||
return aString, true
|
||||
}
|
||||
|
||||
aBytes, isBytes := a.([]byte)
|
||||
if isBytes {
|
||||
return string(aBytes), true
|
||||
}
|
||||
|
||||
aStringer, isStringer := a.(fmt.Stringer)
|
||||
if isStringer {
|
||||
return aStringer.String(), true
|
||||
}
|
||||
|
||||
aJSONRawMessage, isJSONRawMessage := a.(json.RawMessage)
|
||||
if isJSONRawMessage {
|
||||
return string(aJSONRawMessage), true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package matchers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/itchyny/gojq"
|
||||
"github.com/onsi/gomega/format"
|
||||
)
|
||||
|
||||
func ExtractJQ(expression string) func(in any) (any, error) {
|
||||
return func(in any) (any, error) {
|
||||
query, err := gojq.Parse(expression)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actualString, ok := toString(in)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("ExtractJQ requires a string, stringer, or []byte. Got actual:\n%s", format.Object(in, 1))
|
||||
}
|
||||
|
||||
if len(actualString) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
b := []byte(actualString)
|
||||
|
||||
var it gojq.Iter
|
||||
|
||||
// rough check for object vs array
|
||||
switch b[0] {
|
||||
case '{':
|
||||
data := make(map[string]any)
|
||||
if err := json.Unmarshal(b, &data); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
it = query.Run(data)
|
||||
case '[':
|
||||
var data []any
|
||||
if err := json.Unmarshal(b, &data); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
it = query.Run(data)
|
||||
default:
|
||||
return false, fmt.Errorf("ExtractJQ requires a Json Array or Object. Got actual:\n%s", format.Object(in, 1))
|
||||
}
|
||||
|
||||
v, ok := it.Next()
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err, ok := v.(error); ok {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package matchers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
func AsJSON() func(in any) (any, error) {
|
||||
return func(in any) (any, error) {
|
||||
data, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue