Skip to content

Commit

Permalink
More tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
bigkevmcd committed Nov 13, 2024
1 parent 2636e01 commit 891a42c
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 24 deletions.
36 changes: 12 additions & 24 deletions internal/server/cel.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"strings"

"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/common/types/traits"
Expand Down Expand Up @@ -54,6 +53,9 @@ func newCELProgram(expr string) (cel.Program, error) {
if issues != nil && issues.Err() != nil {
return nil, fmt.Errorf("expression %v check failed: %w", expr, issues.Err())
}
if checked.OutputType() != types.BoolType {
return nil, fmt.Errorf("invalid expression output type %v", checked.OutputType())
}

prg, err := env.Program(checked, cel.EvalOptions(cel.OptOptimize), cel.InterruptCheckFrequency(100))
if err != nil {
Expand Down Expand Up @@ -94,26 +96,19 @@ func newCELEvaluator(expr string, req *http.Request) (resourcePredicate, error)
return nil, fmt.Errorf("expression %v failed to evaluate: %w", expr, err)
}

v, ok := out.(types.Bool)
if !ok {
return nil, fmt.Errorf("expression %q did not return a boolean value", expr)
}

result := v.Value().(bool)
result := out.Value().(bool)

return &result, nil
}, nil
}

func makeCELEnv() (*cel.Env, error) {
mapStrDyn := decls.NewMapType(decls.String, decls.Dyn)
return cel.NewEnv(
celext.Strings(),
notifications(),
cel.Declarations(
decls.NewVar("resource", mapStrDyn),
decls.NewVar("request", mapStrDyn),
))
cel.Variable("resource", cel.ObjectType("google.protobuf.Struct")),
cel.Variable("request", cel.ObjectType("google.protobuf.Struct")),
)
}

func isJSONContent(r *http.Request) bool {
Expand All @@ -132,17 +127,10 @@ func isJSONContent(r *http.Request) bool {
}

func notifications() cel.EnvOption {
r, err := types.NewRegistry()
if err != nil {
panic(err) // TODO: Do something better?
}

return cel.Lib(&notificationsLib{registry: r})
return cel.Lib(&notificationsLib{})
}

type notificationsLib struct {
registry *types.Registry
}
type notificationsLib struct{}

// LibraryName implements the SingletonLibrary interface method.
func (*notificationsLib) LibraryName() string {
Expand All @@ -151,13 +139,13 @@ func (*notificationsLib) LibraryName() string {

// CompileOptions implements the Library interface method.
func (l *notificationsLib) CompileOptions() []cel.EnvOption {
listStrDyn := cel.ListType(cel.DynType)
listDyn := cel.ListType(cel.DynType)
opts := []cel.EnvOption{
cel.Function("first",
cel.MemberOverload("first_list", []*cel.Type{listStrDyn}, cel.DynType,
cel.MemberOverload("first_list", []*cel.Type{listDyn}, cel.DynType,
cel.UnaryBinding(listFirst))),
cel.Function("last",
cel.MemberOverload("last_list", []*cel.Type{listStrDyn}, cel.DynType,
cel.MemberOverload("last_list", []*cel.Type{listDyn}, cel.DynType,
cel.UnaryBinding(listLast))),
}

Expand Down
131 changes: 131 additions & 0 deletions internal/server/cel_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package server

import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"testing"

apiv1 "github.com/fluxcd/notification-controller/api/v1"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func TestValidateCELExpressionValidExpressions(t *testing.T) {
validationTests := []string{
"true",
"false",
"request.body.value == 'test'",
}

for _, tt := range validationTests {
t.Run(tt, func(t *testing.T) {
g := NewWithT(t)
g.Expect(ValidateCELExpression(tt)).To(Succeed())
})
}
}

func TestValidateCELExpressionInvalidExpressions(t *testing.T) {
validationTests := []struct {
expression string
wantError string
}{
{
"'test'",
"invalid expression output type string",
},
{
"requrest.body.value",
"undeclared reference to 'requrest'",
},
}

for _, tt := range validationTests {
t.Run(tt.expression, func(t *testing.T) {
g := NewWithT(t)
g.Expect(ValidateCELExpression(tt.expression)).To(MatchError(ContainSubstring(tt.wantError)))
})
}
}

func TestCELEvaluation(t *testing.T) {
evaluationTests := []struct {
expression string
request *http.Request
resource client.Object
wantResult bool
}{
{
expression: `resource.metadata.name == 'test-resource' && request.body.target.repository == 'hello-world'`,
request: testNewHTTPRequest(t, http.MethodPost, "/test", map[string]any{
"target": map[string]any{
"repository": "hello-world",
},
}),
resource: &apiv1.Receiver{
TypeMeta: metav1.TypeMeta{
Kind: apiv1.ReceiverKind,
APIVersion: apiv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-resource",
},
},
wantResult: true,
},
{
expression: `resource.metadata.name == 'test-resource' && request.body.image.source.split(':').last().startsWith('v')`,
request: testNewHTTPRequest(t, http.MethodPost, "/test", map[string]any{
"image": map[string]any{
"source": "hello-world:v1.0.0",
},
}),
resource: &apiv1.Receiver{
TypeMeta: metav1.TypeMeta{
Kind: apiv1.ReceiverKind,
APIVersion: apiv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-resource",
},
},
wantResult: true,
},
}

for _, tt := range evaluationTests {
t.Run(tt.expression, func(t *testing.T) {
g := NewWithT(t)
evaluator, err := newCELEvaluator(tt.expression, tt.request)
g.Expect(err).To(Succeed())

result, err := evaluator(context.Background(), tt.resource)
g.Expect(err).To(Succeed())
g.Expect(result).To(Equal(&tt.wantResult))
})
}
}

func testNewHTTPRequest(t *testing.T, method, target string, body map[string]any) *http.Request {
var httpBody io.Reader
g := NewWithT(t)
if body != nil {
b, err := json.Marshal(body)
g.Expect(err).To(Succeed())
httpBody = bytes.NewReader(b)
}

req, err := http.NewRequest(method, target, httpBody)
g.Expect(err).To(Succeed())

if httpBody != nil {
req.Header.Set("Content-Type", "application/json")
}

return req

}

0 comments on commit 891a42c

Please sign in to comment.