kubernetes-operator/test/support/test.go

299 lines
6.7 KiB
Go

package support
import (
"bytes"
"context"
"net/http"
"sync"
"testing"
"time"
"github.com/anthhub/forwarder"
"k8s.io/client-go/tools/portforward"
"github.com/rs/xid"
netv1 "k8s.io/api/networking/v1"
"github.com/dapr-sandbox/dapr-kubernetes-operator/test/support/helm"
"github.com/dapr-sandbox/dapr-kubernetes-operator/api/operator/v1alpha1"
daprCP "github.com/dapr-sandbox/dapr-kubernetes-operator/internal/controller/operator"
daprAc "github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/client/operator/applyconfiguration/operator/v1alpha1"
"github.com/dapr-sandbox/dapr-kubernetes-operator/pkg/pointer"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
corev1 "k8s.io/api/core/v1"
"github.com/onsi/gomega"
)
//nolint:interfacebloat
type Test interface {
T() *testing.T
Ctx() context.Context
Client() *Client
NewTestNamespace(...Option[*corev1.Namespace]) *corev1.Namespace
NewDaprControlPlane(*daprAc.DaprControlPlaneSpecApplyConfiguration) *v1alpha1.DaprControlPlane
NewNamespacedNameDaprControlPlane(types.NamespacedName, *daprAc.DaprControlPlaneSpecApplyConfiguration) *v1alpha1.DaprControlPlane
InstallChart(string, ...helm.InstallOption)
SetUpIngress(string, netv1.HTTPIngressPath) *netv1.Ingress
Forward(string, string, int) (*forwarder.Result, [][]portforward.ForwardedPort, error)
GET(string) func(g gomega.Gomega) (*http.Response, error)
POST(string, string, []byte) func(g gomega.Gomega) (*http.Response, error)
gomega.Gomega
}
type Option[T any] interface {
applyTo(to T) error
}
type errorOption[T any] func(to T) error
//nolint:unused
func (o errorOption[T]) applyTo(to T) error {
return o(to)
}
var _ Option[any] = errorOption[any](nil)
func With(t *testing.T) Test {
t.Helper()
t.Log()
ctx := context.Background()
if deadline, ok := t.Deadline(); ok {
withDeadline, cancel := context.WithDeadline(ctx, deadline)
t.Cleanup(cancel)
ctx = withDeadline
}
answer := &T{
WithT: gomega.NewWithT(t),
t: t,
ctx: ctx,
}
answer.SetDefaultEventuallyPollingInterval(500 * time.Millisecond)
answer.SetDefaultEventuallyTimeout(TestTimeoutLong)
answer.SetDefaultConsistentlyDuration(500 * time.Millisecond)
answer.SetDefaultConsistentlyDuration(TestTimeoutLong)
return answer
}
type T struct {
*gomega.WithT
t *testing.T
client *Client
once sync.Once
//nolint:containedctx
ctx context.Context
}
func (t *T) T() *testing.T {
return t.t
}
func (t *T) Ctx() context.Context {
return t.ctx
}
func (t *T) Client() *Client {
t.once.Do(func() {
c, err := newClient(t.t.Logf)
if err != nil {
t.T().Fatalf("Error creating client: %v", err)
}
t.client = c
})
return t.client
}
func (t *T) NewTestNamespace(options ...Option[*corev1.Namespace]) *corev1.Namespace {
t.T().Helper()
namespace := createTestNamespace(t, options...)
t.T().Cleanup(func() {
deleteTestNamespace(t, namespace)
})
return namespace
}
func (t *T) NewDaprControlPlane(
spec *daprAc.DaprControlPlaneSpecApplyConfiguration,
) *v1alpha1.DaprControlPlane {
return t.NewNamespacedNameDaprControlPlane(
types.NamespacedName{
Name: daprCP.DaprControlPlaneName,
Namespace: daprCP.DaprControlPlaneNamespaceDefault,
},
spec,
)
}
func (t *T) NewNamespacedNameDaprControlPlane(
nn types.NamespacedName,
spec *daprAc.DaprControlPlaneSpecApplyConfiguration,
) *v1alpha1.DaprControlPlane {
t.T().Logf("Setting up Dapr ControlPlane %s in namespace %s", nn.Name, nn.Namespace)
cp := t.Client().Dapr.OperatorV1alpha1().DaprControlPlanes(nn.Namespace)
instance, err := cp.Apply(
t.Ctx(),
daprAc.DaprControlPlane(nn.Name, nn.Namespace).
WithSpec(spec),
metav1.ApplyOptions{
FieldManager: "dapr-e2e-" + t.T().Name(),
})
t.Expect(err).
ToNot(gomega.HaveOccurred())
t.T().Cleanup(func() {
t.Expect(
cp.Delete(t.Ctx(), instance.Name, metav1.DeleteOptions{
PropagationPolicy: pointer.Any(metav1.DeletePropagationForeground),
}),
).ToNot(gomega.HaveOccurred())
})
return instance
}
func (t *T) InstallChart(
chart string,
options ...helm.InstallOption,
) {
allopt := make([]helm.InstallOption, 0)
allopt = append(allopt, options...)
allopt = append(allopt, helm.WithInstallTimeout(TestTimeoutLong))
release, err := t.Client().Helm.Install(
t.Ctx(),
chart,
allopt...)
t.Expect(err).
ToNot(gomega.HaveOccurred())
t.T().Cleanup(func() {
err := t.Client().Helm.Uninstall(
t.Ctx(),
release.Name,
helm.WithUninstallTimeout(TestTimeoutLong))
t.Expect(err).
ToNot(gomega.HaveOccurred())
})
}
func (t *T) SetUpIngress(
namespace string,
path netv1.HTTPIngressPath,
) *netv1.Ingress {
name := xid.New().String()
t.T().Logf("Setting up ingress %s in namespace %s", name, namespace)
ing, err := t.Client().NetworkingV1().Ingresses(namespace).Create(
t.Ctx(),
&netv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: netv1.IngressSpec{
Rules: []netv1.IngressRule{{
IngressRuleValue: netv1.IngressRuleValue{
HTTP: &netv1.HTTPIngressRuleValue{
Paths: []netv1.HTTPIngressPath{path},
},
},
}},
},
},
metav1.CreateOptions{},
)
t.Expect(err).
ToNot(gomega.HaveOccurred())
t.Expect(ing).
ToNot(gomega.BeNil())
t.T().Cleanup(func() {
t.Expect(
t.Client().NetworkingV1().Ingresses(namespace).Delete(
t.Ctx(),
ing.Name,
metav1.DeleteOptions{
PropagationPolicy: pointer.Any(metav1.DeletePropagationForeground),
},
),
).ToNot(gomega.HaveOccurred())
})
return ing
}
func (t *T) Forward(service string, namespace string, remotePort int) (*forwarder.Result, [][]portforward.ForwardedPort, error) {
options := []*forwarder.Option{{
RemotePort: remotePort,
ServiceName: service,
Namespace: namespace,
}}
ret, err := forwarder.WithRestConfig(t.Ctx(), options, t.Client().config)
if err != nil {
return nil, nil, err
}
ports, err := ret.Ready()
if err != nil {
return nil, nil, err
}
return ret, ports, nil
}
func (t *T) GET(url string) func(g gomega.Gomega) (*http.Response, error) {
return func(g gomega.Gomega) (*http.Response, error) {
req, err := http.NewRequestWithContext(t.Ctx(), http.MethodGet, url, nil)
if err != nil {
return nil, err
}
return http.DefaultClient.Do(req)
}
}
func (t *T) POST(url string, contentType string, content []byte) func(g gomega.Gomega) (*http.Response, error) {
return func(g gomega.Gomega) (*http.Response, error) {
data := content
if data == nil {
data = []byte{}
}
req, err := http.NewRequestWithContext(t.Ctx(), http.MethodPost, url, bytes.NewReader(data))
if err != nil {
return nil, err
}
if contentType != "" {
req.Header.Add("Content-Type", contentType)
}
return http.DefaultClient.Do(req)
}
}