diff --git a/testsupport/assertions/assertions.go b/testsupport/assertions/assertions.go new file mode 100644 index 000000000..3538037d4 --- /dev/null +++ b/testsupport/assertions/assertions.go @@ -0,0 +1,54 @@ +package assertions + +// Assertion is a test function that is meant to test some object. +type Assertion[T any] interface { + Test(t AssertT, obj T) +} + +// Assertions is just a list of assertions provided for convenience. It is meant to be embedded into structs. +type Assertions[T any] []Assertion[T] + +// AssertionFunc converts a function into an assertion. +type AssertionFunc[T any] func(t AssertT, obj T) + +// Append is just an alias for Go's built-in append. +func Append[Type any](assertionList Assertions[Type], assertions ...Assertion[Type]) Assertions[Type] { + assertionList = append(assertionList, assertions...) + return assertionList +} + +// AppendGeneric is a variant of append that can also cast assertions on some super-type into assertions +// on some type. This can be useful when one has some assertions that work on an super-type of some type and +// you want to append it to a list of assertions on the type itself. +func AppendGeneric[SuperType any, Type any](assertionList Assertions[Type], assertions ...Assertion[SuperType]) Assertions[Type] { + for _, a := range assertions { + assertionList = append(assertionList, CastAssertion[SuperType, Type](a)) + } + return assertionList +} + +// AppendConverted is a convenience function to first lift all the assertions to the "To" type and then append them to the provided list. +func AppendConverted[From any, To any](conversion func(To) (From, bool), assertionList Assertions[To], assertions ...Assertion[From]) Assertions[To] { + return Append(assertionList, ConvertAll(conversion, assertions...)...) +} + +// AppendFunc is a convenience function that is able to take in the assertions as simple functions. +func AppendFunc[T any](assertionList Assertions[T], fn ...AssertionFunc[T]) Assertions[T] { + for _, f := range fn { + assertionList = append(assertionList, f) + } + return assertionList +} + +// Test runs the test by all assertions in the list. +func (as Assertions[T]) Test(t AssertT, obj T) { + t.Helper() + for _, a := range as { + a.Test(t, obj) + } +} + +func (f AssertionFunc[T]) Test(t AssertT, obj T) { + t.Helper() + f(t, obj) +} diff --git a/testsupport/assertions/conditions/conditions.go b/testsupport/assertions/conditions/conditions.go new file mode 100644 index 000000000..104670e90 --- /dev/null +++ b/testsupport/assertions/conditions/conditions.go @@ -0,0 +1,47 @@ +package conditions + +import ( + toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1" + "github.com/codeready-toolchain/toolchain-common/pkg/condition" + "github.com/codeready-toolchain/toolchain-e2e/testsupport/assertions" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" +) + +type ConditionAssertions struct { + assertions.Assertions[[]toolchainv1alpha1.Condition] +} + +func With() *ConditionAssertions { + return &ConditionAssertions{} +} + +func (cas *ConditionAssertions) Type(typ toolchainv1alpha1.ConditionType) *ConditionAssertions { + cas.Assertions = assertions.AppendFunc(cas.Assertions, func(t assertions.AssertT, conds []toolchainv1alpha1.Condition) { + t.Helper() + _, found := condition.FindConditionByType(conds, typ) + assert.True(t, found, "didn't find a condition with the type '%v'", typ) + }) + return cas +} + +func (cas *ConditionAssertions) Status(typ toolchainv1alpha1.ConditionType, status corev1.ConditionStatus) *ConditionAssertions { + cas.Assertions = assertions.AppendFunc(cas.Assertions, func(t assertions.AssertT, conds []toolchainv1alpha1.Condition) { + t.Helper() + cond, found := condition.FindConditionByType(conds, typ) + assert.True(t, found, "didn't find a condition with the type '%v'", typ) + assert.Equal(t, status, cond.Status, "condition of type '%v' doesn't have the expected status", typ) + }) + return cas +} + +func (cas *ConditionAssertions) StatusAndReason(typ toolchainv1alpha1.ConditionType, status corev1.ConditionStatus, reason string) *ConditionAssertions { + cas.Assertions = assertions.AppendFunc(cas.Assertions, func(t assertions.AssertT, conds []toolchainv1alpha1.Condition) { + t.Helper() + cond, found := condition.FindConditionByType(conds, typ) + assert.True(t, found, "didn't find a condition with the type '%v'", typ) + assert.Equal(t, status, cond.Status, "condition of type '%v' doesn't have the expected status", typ) + assert.Equal(t, reason, cond.Reason, "condition of type '%v' doesn't have the expected reason", typ) + }) + return cas +} diff --git a/testsupport/assertions/convert.go b/testsupport/assertions/convert.go new file mode 100644 index 000000000..6f134ed24 --- /dev/null +++ b/testsupport/assertions/convert.go @@ -0,0 +1,76 @@ +package assertions + +// CastAssertion can be used to convert a generic assertion on, say, client.Object, into +// an assertion on a concrete subtype. Note that the conversion is not guaranteed to +// pass by the type system and can fail at runtime. +func CastAssertion[SuperType any, Type any](a Assertion[SuperType]) Assertion[Type] { + // we cannot pass "cast[SuperType]" as a function pointer, so we need this aid + conversion := func(o Type) (SuperType, bool) { + return cast[SuperType](o) + } + + return Convert(conversion, a) +} + +// Convert converts from one assertion type to another by converting the tested value. +// It respectes the ObjectNameAssertion and ObjectNamespaceAssertion so that assertions +// can still be used to identify the object after conversion. +// The provided accessor can be fallible, returning false on the failure to convert the object. +func Convert[From any, To any](accessor func(To) (From, bool), assertion Assertion[From]) Assertion[To] { + if _, ok := assertion.(ObjectNameAssertion); ok { + return &convertedObjectName[From, To]{convertedAssertion: convertedAssertion[From, To]{accessor: accessor, assertion: assertion}} + } else if _, ok := assertion.(ObjectNamespaceAssertion); ok { + return &convertedObjectNamespace[From, To]{convertedAssertion: convertedAssertion[From, To]{accessor: accessor, assertion: assertion}} + } else { + return &convertedAssertion[From, To]{accessor: accessor, assertion: assertion} + } +} + +// ConvertAll performs Convert on all the provided assertions. +func ConvertAll[From any, To any](accessor func(To) (From, bool), assertions ...Assertion[From]) Assertions[To] { + tos := make(Assertions[To], len(assertions)) + for i, a := range assertions { + tos[i] = Convert(accessor, a) + } + return tos +} + +// cast casts the obj into T. This is strangely required in cases where you want to cast +// object that is typed using a type parameter into a type specified by another type parameter. +// The compiler rejects such casts but doesn't complain if the cast is done using +// an indirection using this function. +func cast[T any](obj any) (T, bool) { + ret, ok := obj.(T) + return ret, ok +} + +type convertedAssertion[From any, To any] struct { + assertion Assertion[From] + accessor func(To) (From, bool) +} + +func (ca *convertedAssertion[From, To]) Test(t AssertT, obj To) { + t.Helper() + o, ok := ca.accessor(obj) + if !ok { + t.Errorf("invalid conversion") + return + } + ca.assertion.Test(t, o) +} + +type convertedObjectName[From any, To any] struct { + convertedAssertion[From, To] +} + +func (con *convertedObjectName[From, To]) Name() string { + return con.assertion.(ObjectNameAssertion).Name() +} + +type convertedObjectNamespace[From any, To any] struct { + convertedAssertion[From, To] +} + +func (con *convertedObjectNamespace[From, To]) Namespace() string { + return con.assertion.(ObjectNamespaceAssertion).Namespace() +} diff --git a/testsupport/assertions/metadata/metadata.go b/testsupport/assertions/metadata/metadata.go new file mode 100644 index 000000000..a9fe1903b --- /dev/null +++ b/testsupport/assertions/metadata/metadata.go @@ -0,0 +1,91 @@ +package metadata + +import ( + "github.com/codeready-toolchain/toolchain-e2e/testsupport/assertions" + "github.com/stretchr/testify/assert" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MetadataAssertions is a set of assertions on the metadata of any client.Object. +type MetadataAssertions struct { + assertions.Assertions[client.Object] +} + +// With is a "readable" constructor of MetadataAssertions. It is meant to be used +// to construct the MetadataAssertions instance so that the call reads like an English +// sentence: "metadata.With().Name().Namespace()..." +func With() *MetadataAssertions { + return &MetadataAssertions{} +} + +// objectName is a special impl of an assertion on object name that also implements +// the assertions.ObjectNameAssertion so that it can be used in await methods to +// identify the object. +type objectName struct { + name string +} + +// objectName is a special impl of an assertion on object name that also implements +// the assertions.ObjectNamespaceAssertion so that it can be used in await methods to +// identify the object. +type objectNamespace struct { + namespace string +} + +// Name adds an assertion on the objects name being equal to the provided value. +// The assertion also implements the assertions.ObjectNameAssertion so that it can be +// transparently used to identify the object during the assertions.Await calls. +func (ma *MetadataAssertions) Name(name string) *MetadataAssertions { + ma.Assertions = assertions.Append(ma.Assertions, &objectName{name: name}) + return ma +} + +// Name adds an assertion on the objects namespace being equal to the provided value. +// The assertion also implements the assertions.ObjectNamespaceAssertion so that it can be +// transparently used to identify the object during the assertions.Await calls. +func (ma *MetadataAssertions) Namespace(ns string) *MetadataAssertions { + ma.Assertions = assertions.Append(ma.Assertions, &objectNamespace{namespace: ns}) + return ma +} + +// Label adds an assertion for the presence of the label on the object. +func (ma *MetadataAssertions) Label(name string) *MetadataAssertions { + ma.Assertions = assertions.AppendFunc(ma.Assertions, func(t assertions.AssertT, obj client.Object) { + t.Helper() + assert.Contains(t, obj.GetLabels(), name, "no label called '%s' found on the object", name) + }) + return ma +} + +func (ma *MetadataAssertions) NoLabel(name string) *MetadataAssertions { + ma.Assertions = assertions.AppendFunc(ma.Assertions, func(t assertions.AssertT, obj client.Object) { + t.Helper() + assert.NotContains(t, obj.GetLabels(), name, "a label called '%s' found on the object but none expected", name) + }) + return ma +} + +func (a *objectName) Test(t assertions.AssertT, obj client.Object) { + t.Helper() + assert.Equal(t, a.name, obj.GetName(), "object name doesn't match") +} + +func (a *objectName) Name() string { + return a.name +} + +func (a *objectNamespace) Test(t assertions.AssertT, obj client.Object) { + t.Helper() + assert.Equal(t, a.namespace, obj.GetNamespace(), "object namespace doesn't match") +} + +func (a *objectNamespace) Namespace() string { + return a.namespace +} + +var ( + _ assertions.Assertion[client.Object] = (*objectName)(nil) + _ assertions.Assertion[client.Object] = (*objectNamespace)(nil) + _ assertions.ObjectNameAssertion = (*objectName)(nil) + _ assertions.ObjectNamespaceAssertion = (*objectNamespace)(nil) +) diff --git a/testsupport/assertions/object.go b/testsupport/assertions/object.go new file mode 100644 index 000000000..3cc777dfd --- /dev/null +++ b/testsupport/assertions/object.go @@ -0,0 +1,281 @@ +package assertions + +import ( + "context" + "encoding/json" + "fmt" + "reflect" + "strings" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + kwait "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + DefaultRetryInterval = time.Millisecond * 100 // make it short because a "retry interval" is waited before the first test + DefaultTimeout = time.Second * 120 +) + +type AddressableObjectAssertions[T client.Object] struct { + Assertions[T] +} + +type ObjectNameAssertion interface { + Name() string +} + +type ObjectNamespaceAssertion interface { + Namespace() string +} + +type Await[T client.Object] struct { + cl client.Client + timeout time.Duration + tick time.Duration + assertions Assertions[T] +} + +type logger interface { + Logf(format string, args ...any) +} + +type errorCollectingT struct { + errors []error + logger + failed bool +} + +func (oa *AddressableObjectAssertions[T]) Await(cl client.Client) *Await[T] { + return &Await[T]{ + cl: cl, + timeout: DefaultTimeout, + tick: DefaultRetryInterval, + assertions: oa.Assertions, + } +} + +func (f *Await[T]) WithTimeout(timeout time.Duration) *Await[T] { + f.timeout = timeout + return f +} + +func (f *Await[T]) WithRetryInterval(interval time.Duration) *Await[T] { + f.tick = interval + return f +} + +func (f *Await[T]) First(ctx context.Context, t RequireT) T { + t.Helper() + + namespace, found := findNamespaceFromAssertions(f.assertions) + require.True(t, found, "no ObjectNamespaceAssertion found in the assertions but one required") + + t.Logf("waiting for the first object of type %T in namespace '%s' to match criteria", newObject[T](), namespace) + + possibleGvks, _, err := f.cl.Scheme().ObjectKinds(newObject[T]()) + require.NoError(t, err) + require.Len(t, possibleGvks, 1) + + gvk := possibleGvks[0] + + var returnedObject T + + ft := &errorCollectingT{logger: t} + + err = kwait.PollUntilContextTimeout(ctx, f.tick, f.timeout, true, func(ctx context.Context) (done bool, err error) { + list := &unstructured.UnstructuredList{} + list.SetGroupVersionKind(gvk) + ft.errors = nil + if err := f.cl.List(ctx, list, client.InNamespace(namespace)); err != nil { + return false, err + } + for _, uobj := range list.Items { + uobj := uobj + obj, err := remarshal[T](f.cl.Scheme(), &uobj) + if err != nil { + return false, fmt.Errorf("failed to cast object with GVK %v to object %T: %w", gvk, newObject[T](), err) + } + + f.assertions.Test(ft, obj) + + if !ft.failed { + returnedObject = obj + } + } + return !ft.failed, nil + }) + if err != nil { + sb := strings.Builder{} + sb.WriteString("failed to find objects (of GVK '%s') in namespace '%s' matching the criteria: %s") + args := []any{gvk, namespace, err.Error()} + list := &unstructured.UnstructuredList{} + list.SetGroupVersionKind(gvk) + if err := f.cl.List(context.TODO(), list, client.InNamespace(namespace)); err != nil { + sb.WriteString(" and also failed to retrieve the object at all with error: %s") + args = append(args, err) + } else { + sb.WriteString("\nlisting the objects found in cluster with the differences from the expected state for each:") + for _, o := range list.Items { + o := o + obj, _ := remarshal[T](f.cl.Scheme(), &o) + key := client.ObjectKeyFromObject(obj) + + sb.WriteRune('\n') + sb.WriteString("object ") + sb.WriteString(key.String()) + sb.WriteString(":\nSome of the assertions failed to match the object (see output above).") + } + } + t.Logf(sb.String(), args...) + } + + return returnedObject +} + +func (f *Await[T]) Matching(ctx context.Context, t RequireT) T { + t.Helper() + + name, found := findNameFromAssertions(f.assertions) + require.True(t, found, "ObjectNameAssertion not found in the list of assertions but one is required") + + namespace, found := findNamespaceFromAssertions(f.assertions) + require.True(t, found, "ObjectNamespaceAssertion not found in the list of assertions but one is required") + + key := client.ObjectKey{Name: name, Namespace: namespace} + + t.Logf("waiting for %T with name '%s' in namespace '%s' to match additional criteria", newObject[T](), key.Name, key.Namespace) + + var returnedObject T + + ft := &errorCollectingT{logger: t} + + err := kwait.PollUntilContextTimeout(ctx, f.tick, f.timeout, true, func(ctx context.Context) (done bool, err error) { + t.Helper() + ft.errors = nil + obj := newObject[T]() + err = f.cl.Get(ctx, key, obj) + if err != nil { + assert.NoError(ft, err, "failed to find the object by key %s", key) + return false, err + } + + f.assertions.Test(ft, obj) + + if !ft.failed { + returnedObject = obj + } + + return !ft.failed, nil + }) + if err != nil { + if ft.failed { + for _, e := range ft.errors { + t.Errorf("%s", e) //nolint: testifylint + } + obj := newObject[T]() + err := f.cl.Get(ctx, key, obj) + if err != nil { + t.Errorf("failed to find the object while reporting the failure to match by criteria using object key %s", key) + return returnedObject + } + t.Logf("Some of the assertions failed to match the object (see output above).") + } + t.Logf("couldn't match %T with name '%s' in namespace '%s' with additional criteria because of: %s", newObject[T](), key.Name, key.Namespace, err) + } + + return returnedObject +} + +func (f *Await[T]) Deleted(ctx context.Context, t RequireT) { + t.Helper() + + name, found := findNameFromAssertions(f.assertions) + require.True(t, found, "ObjectNameAssertion not found in the list of assertions but one is required") + + namespace, found := findNamespaceFromAssertions(f.assertions) + require.True(t, found, "ObjectNamespaceAssertion not found in the list of assertions but one is required") + + key := client.ObjectKey{Name: name, Namespace: namespace} + + err := kwait.PollUntilContextTimeout(ctx, f.tick, f.timeout, true, func(ctx context.Context) (done bool, err error) { + obj := newObject[T]() + err = f.cl.Get(ctx, key, obj) + if err != nil && apierrors.IsNotFound(err) { + return true, nil + } + return false, err + }) + if err != nil { + assert.Fail(t, "object with key %s still present or other error happened: %s", key, err) + } +} + +func (t *errorCollectingT) Errorf(format string, args ...any) { + t.failed = true + t.errors = append(t.errors, fmt.Errorf(format, args...)) +} + +func (f *errorCollectingT) Helper() { + // we cannot call any inner Helper() because that wouldn't work anyway +} + +func (f *errorCollectingT) FailNow() { + panic("assertion failed") +} + +func remarshal[T client.Object](scheme *runtime.Scheme, obj *unstructured.Unstructured) (T, error) { + var empty T + raw, err := obj.MarshalJSON() + if err != nil { + return empty, fmt.Errorf("failed to obtain the raw JSON of the object: %w", err) + } + + typed, err := scheme.New(obj.GroupVersionKind()) + if err != nil { + return empty, fmt.Errorf("failed to create a new empty object from the scheme: %w", err) + } + + err = json.Unmarshal(raw, typed) + if err != nil { + return empty, fmt.Errorf("failed to unmarshal the raw JSON to the go structure: %w", err) + } + + return typed.(T), nil +} + +func newObject[T client.Object]() T { + // all client.Object implementations are pointers, so this declaration gives us just a nil pointer + var v T + + ptrT := reflect.TypeOf(v) + valT := ptrT.Elem() + ptrToZeroV := reflect.New(valT) + + zero := ptrToZeroV.Interface() + + return zero.(T) +} + +func findNameFromAssertions[T any](as []Assertion[T]) (string, bool) { + for _, a := range as { + if oa, ok := a.(ObjectNameAssertion); ok { + return oa.Name(), true + } + } + return "", false +} + +func findNamespaceFromAssertions[T any](as []Assertion[T]) (string, bool) { + for _, a := range as { + if oa, ok := a.(ObjectNamespaceAssertion); ok { + return oa.Namespace(), true + } + } + return "", false +} diff --git a/testsupport/assertions/object/object.go b/testsupport/assertions/object/object.go new file mode 100644 index 000000000..9aee1a838 --- /dev/null +++ b/testsupport/assertions/object/object.go @@ -0,0 +1,32 @@ +package object + +import ( + "github.com/codeready-toolchain/toolchain-e2e/testsupport/assertions" + "github.com/codeready-toolchain/toolchain-e2e/testsupport/assertions/metadata" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ObjectAssertions is a common base for assertions on client.Object subtypes +// and is meant to be embedded in other structs. +// +// It provides assertions on the object metadata. +// +// It is necessary to initialize this using the SetFluentSelf method so that +// the methods defined on this struct can participate in the fluent call-chains +// defined on the struct that embeds it. +type ObjectAssertions[Self any, T client.Object] struct { + assertions.AddressableObjectAssertions[T] + self *Self +} + +// SetFluentSelf sets the "Self" that should be returned from the fluent methods like +// ObjectMeta(). +func (oa *ObjectAssertions[Self, T]) SetFluentSelf(self *Self) { + oa.self = self +} + +// ObjectMeta sets the assertions on the metadata of the object. +func (oa *ObjectAssertions[Self, T]) ObjectMeta(mas *metadata.MetadataAssertions) *Self { + oa.Assertions = assertions.AppendGeneric(oa.Assertions, mas.Assertions...) + return oa.self +} diff --git a/testsupport/assertions/spaceprovisionerconfig/spc.go b/testsupport/assertions/spaceprovisionerconfig/spc.go new file mode 100644 index 000000000..d81ab2f07 --- /dev/null +++ b/testsupport/assertions/spaceprovisionerconfig/spc.go @@ -0,0 +1,36 @@ +package spaceprovisionerconfig + +import ( + toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1" + "github.com/codeready-toolchain/toolchain-e2e/testsupport/assertions" + "github.com/codeready-toolchain/toolchain-e2e/testsupport/assertions/conditions" + "github.com/codeready-toolchain/toolchain-e2e/testsupport/assertions/object" + "github.com/stretchr/testify/assert" +) + +type SpaceProvisionerConfigAssertions struct { + object.ObjectAssertions[SpaceProvisionerConfigAssertions, *toolchainv1alpha1.SpaceProvisionerConfig] +} + +func Asserting() *SpaceProvisionerConfigAssertions { + spc := &SpaceProvisionerConfigAssertions{} + spc.SetFluentSelf(spc) + return spc +} + +func (spc *SpaceProvisionerConfigAssertions) Conditions(cas *conditions.ConditionAssertions) *SpaceProvisionerConfigAssertions { + spc.Assertions = assertions.AppendConverted(getConditions, spc.Assertions, cas.Assertions...) + return spc +} + +func (spc *SpaceProvisionerConfigAssertions) ToolchainClusterName(name string) *SpaceProvisionerConfigAssertions { + spc.Assertions = assertions.AppendFunc(spc.Assertions, func(t assertions.AssertT, obj *toolchainv1alpha1.SpaceProvisionerConfig) { + t.Helper() + assert.Equal(t, name, obj.Spec.ToolchainCluster, "unexpected toolchainCluster") + }) + return spc +} + +func getConditions(spc *toolchainv1alpha1.SpaceProvisionerConfig) ([]toolchainv1alpha1.Condition, bool) { + return spc.Status.Conditions, true +} diff --git a/testsupport/assertions/t.go b/testsupport/assertions/t.go new file mode 100644 index 000000000..df004d892 --- /dev/null +++ b/testsupport/assertions/t.go @@ -0,0 +1,29 @@ +package assertions + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type AssertT interface { + assert.TestingT + Helper() + Logf(format string, args ...any) +} + +type RequireT interface { + require.TestingT + Helper() + Logf(format string, args ...any) +} + +type failureTrackingT struct { + AssertT + failed bool +} + +func (t *failureTrackingT) Errorf(format string, args ...any) { + t.Helper() + t.failed = true + t.AssertT.Errorf(format, args...) +} diff --git a/testsupport/assertions_use/assertions_test.go b/testsupport/assertions_use/assertions_test.go new file mode 100644 index 000000000..19383c7f7 --- /dev/null +++ b/testsupport/assertions_use/assertions_test.go @@ -0,0 +1,44 @@ +package assertions_use + +import ( + "context" + "testing" + "time" + + toolchainv1aplha1 "github.com/codeready-toolchain/api/api/v1alpha1" + "github.com/codeready-toolchain/toolchain-e2e/testsupport/assertions/conditions" + "github.com/codeready-toolchain/toolchain-e2e/testsupport/assertions/metadata" + "github.com/codeready-toolchain/toolchain-e2e/testsupport/assertions/spaceprovisionerconfig" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func Test(t *testing.T) { + spcUnderTest := &toolchainv1aplha1.SpaceProvisionerConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myobj", + Namespace: "default", + }, + } + + scheme := runtime.NewScheme() + require.NoError(t, toolchainv1aplha1.AddToScheme(scheme)) + + // use the assertions in a simple immediate call + // spaceprovisionerconfig.Asserting(). + // ObjectMeta(metadata.With().Name("asdf").Label("kachny")). + // Conditions(conditions.With().Type(toolchainv1aplha1.ConditionReady)). + // Test(t, spcUnderTest) + + cl := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(spcUnderTest).Build() + + // this is the new WaitFor + spaceprovisionerconfig.Asserting(). + ObjectMeta(metadata.With().Name("myobj").Namespace("default").Label("requiredLabel")). + Conditions(conditions.With().Type(toolchainv1aplha1.ConditionReady)). + Await(cl). + WithTimeout(1*time.Second). + Matching(context.TODO(), t) +}