Skip to content
This repository has been archived by the owner on Feb 16, 2024. It is now read-only.

improve-factory #40

Merged
merged 9 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pkg/apis/testharness/v1beta1/test_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ type AssertArray struct {
// Path indicates the location within the YAML file to extract data for verification.
Path string `json:"path"`
// Strategy defines how the extracted data should be compared against the Kubernetes resource.
Strategy Strategy `json:"strategy"`
Strategy Strategy `json:"strategy"`
Match *metav1.PartialObjectMetadata `json:"match,omitempty"`
}

// UnmarshalJSON implements the json.Unmarshaller interface.
Expand Down
10 changes: 9 additions & 1 deletion pkg/apis/testharness/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 24 additions & 1 deletion pkg/test/step.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"
"time"

wildcard "github.com/IGLOU-EU/go-wildcard"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -426,13 +427,35 @@ func (s *Step) CheckResourceAbsent(expected runtime.Object, namespace string) er
return fmt.Errorf("resource %s %s (and %d other resources) matched error assertion", unexpectedObjects[0].GroupVersionKind(), unexpectedObjects[0].GetName(), len(unexpectedObjects)-1)
}

// pathMatches checks if the given path matches the pattern.
func pathMatches(pattern, path string) bool {
return wildcard.Match(strings.TrimSuffix(pattern, "/"), path)
}

func metaTypeMatches(assertArray harness.AssertArray, obj client.Object) bool {
if assertArray.Match != nil {
expected, err := runtime.DefaultUnstructuredConverter.ToUnstructured(assertArray.Match)
if err != nil {
return false
}
actual, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return false
}
if err := testutils.IsSubset(expected, actual, "/", testutils.DefaultStrategyFactory()); err != nil {
return false
}
}
return true
}

// Build StrategyFactory for IsSubset
func NewStrategyFactory(a asserts) func(path string) testutils.ArrayComparisonStrategy {
var strategyFactory func(path string) testutils.ArrayComparisonStrategy
recursiveStrategyFactory := func(path string) testutils.ArrayComparisonStrategy {
if a.options != nil && len(a.options.AssertArray) > 0 {
for _, assertArr := range a.options.AssertArray {
if assertArr.Path == path {
if pathMatches(assertArr.Path, path) && metaTypeMatches(assertArr, a.object) {
switch assertArr.Strategy {
case harness.StrategyExact:
return testutils.StrategyExact(path, strategyFactory)
Expand Down
153 changes: 153 additions & 0 deletions pkg/test/step_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -396,3 +397,155 @@ func TestPopulateObjectsByFileName(t *testing.T) {
})
}
}

func TestPathMatches(t *testing.T) {
tests := []struct {
name string
pattern string
path string
expected bool
}{
{
name: "Exact match",
pattern: "/api/v1/users",
path: "/api/v1/users",
expected: true,
},
{
name: "Wildcard at end matches",
pattern: "/api/v1/*",
path: "/api/v1/users",
expected: true,
},
{
name: "Multiple wildcards match",
pattern: "/api/*/users/*",
path: "/api/v1/users/1234",
expected: true,
},
{
name: "Wildcard in middle doesn't match",
pattern: "/api/*/users",
path: "/api/v1/admins",
expected: false,
},
{
name: "Mismatch at the end",
pattern: "/api/v1/users",
path: "/api/v1/users/extra",
expected: false,
},
{
name: "Pattern with trailing slash",
pattern: "/api/v1/users/",
path: "/api/v1/users",
expected: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := pathMatches(test.pattern, test.path)
assert.Equal(t, test.expected, result)
})
}
}

func TestMetaTypeMatches(t *testing.T) {
tests := []struct {
name string
assertArray harness.AssertArray
obj *metav1.PartialObjectMetadata
expectedBool bool
}{
{
name: "Matching Subset",
assertArray: harness.AssertArray{
Match: &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"key": "value",
},
},
},
},
obj: &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"key": "value",
"foo": "bar",
},
},
},
expectedBool: true,
},
{
name: "Non-matching Subset",
assertArray: harness.AssertArray{
Match: &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"key": "wrongValue",
},
},
},
},
obj: &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"key": "value",
"foo": "bar",
},
},
},
expectedBool: false,
},
{
name: "Empty Annotations in Match",
assertArray: harness.AssertArray{
Match: &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{},
},
},
},
obj: &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
"key": "value",
"foo": "bar",
},
},
},
expectedBool: true,
},
{
name: "Using Labels for Matching",
assertArray: harness.AssertArray{
Match: &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "test",
},
},
},
},
obj: &metav1.PartialObjectMetadata{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "test",
"env": "prod",
},
},
},
expectedBool: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := metaTypeMatches(test.assertArray, test.obj)
assert.Equal(t, test.expectedBool, result)
})
}
}
6 changes: 3 additions & 3 deletions pkg/test/utils/subset.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import (
type ArrayComparisonStrategyFactory = func(path string) ArrayComparisonStrategy
type ArrayComparisonStrategy = func(expectedData, actualData interface{}) error

func defaultStrategyFactory() ArrayComparisonStrategyFactory {
func DefaultStrategyFactory() ArrayComparisonStrategyFactory {
return alwaysExact
}

func alwaysExact(path string) ArrayComparisonStrategy {
return StrategyExact(path, defaultStrategyFactory())
return StrategyExact(path, DefaultStrategyFactory())
}

// SubsetError is an error type used by IsSubset for tracking the path in the struct.
Expand Down Expand Up @@ -61,7 +61,7 @@ func IsSubset(expected, actual interface{}, currentPath string, strategyFactory
switch reflect.TypeOf(expected).Kind() {
case reflect.Slice:
if strategyFactory == nil {
strategyFactory = defaultStrategyFactory()
strategyFactory = DefaultStrategyFactory()
}
strategy := strategyFactory(currentPath)

Expand Down
Loading