func splitHostPort(hostPortStr string) (string, int, error) {
	spl := strings.Split(hostPortStr, ":")
	if len(spl) != 2 {
		return "", 0, fmt.Errorf("invalid host:port: %s", hostPortStr)
	}
	host := spl[0]
	port, err := strconv.Atoi(spl[1])
	if err != nil {
		return "", 0, errors.Wrap(err, "port was invalid")
	}
	return host, port, nil
}
similar to net.SplitHostPort (https://pkg.go.dev/net#SplitHostPort)
but returns the port as a string, not an int.

useful because url.Host can contain the port, so ensure we only get the actual host
package main
import (
	"context"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strconv"
	"strings"
	"testing"
	"time"
	"github.com/go-logr/logr"
	"github.com/kedacore/http-add-on/pkg/k8s"
	kedanet "github.com/kedacore/http-add-on/pkg/net"
	"github.com/kedacore/http-add-on/pkg/routing"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.org/x/sync/errgroup"
	appsv1 "k8s.io/api/apps/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
)
func TestIntegrationHappyPath(t *testing.T) {
	const (
		deploymentReplicasTimeout = 200 * time.Millisecond
		responseHeaderTimeout     = 1 * time.Second
		deplName                  = "testdeployment"
	)
	r := require.New(t)
	h, err := newHarness(deploymentReplicasTimeout, responseHeaderTimeout)
	r.NoError(err)
	defer h.close()
	t.Logf("Harness: %s", h.String())
	h.deplCache.Set(deplName, appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{Name: deplName},
		Spec: appsv1.DeploymentSpec{
			Replicas: i32Ptr(3),
		},
		Status: appsv1.DeploymentStatus{
			ReadyReplicas: 3,
		},
	})
	originPort, err := strconv.Atoi(h.originURL.Port())
	r.NoError(err)
	h.routingTable.AddTarget(hostForTest(t), targetFromURL(
		h.originURL,
		originPort,
		deplName,
		123,
	))
	res, err := doRequest(
		http.DefaultClient,
		"GET",
		h.proxyURL.String(),
		hostForTest(t),
		nil,
	)
	r.NoError(err)
	r.Equal(200, res.StatusCode)
}
func TestIntegrationNoRoutingTableEntry(t *testing.T) {
	host := fmt.Sprintf("%s.integrationtest.interceptor.kedahttp.dev", t.Name())
	r := require.New(t)
	h, err := newHarness(time.Second, time.Second)
	r.NoError(err)
	defer h.close()
	h.deplCache.Set(host, appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{Name: host},
		Spec: appsv1.DeploymentSpec{
			Replicas: i32Ptr(1),
		},
	})
	res, err := doRequest(
		http.DefaultClient,
		"GET",
		h.proxyURL.String(),
		"not-in-the-table",
		nil,
	)
	r.NoError(err)
	r.Equal(404, res.StatusCode)
}
func TestIntegrationNoReplicas(t *testing.T) {
	const (
		deployTimeout = 100 * time.Millisecond
	)
	host := hostForTest(t)
	deployName := "testdeployment"
	r := require.New(t)
	h, err := newHarness(deployTimeout, time.Second)
	r.NoError(err)
	originPort, err := strconv.Atoi(h.originURL.Port())
	r.NoError(err)
	h.routingTable.AddTarget(hostForTest(t), targetFromURL(
		h.originURL,
		originPort,
		deployName,
		123,
	))
	h.deplCache.Set(deployName, appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{Name: deployName},
		Spec: appsv1.DeploymentSpec{
			Replicas: i32Ptr(0),
		},
	})
	start := time.Now()
	res, err := doRequest(
		http.DefaultClient,
		"GET",
		h.proxyURL.String(),
		host,
		nil,
	)
	r.NoError(err)
	r.Equal(502, res.StatusCode)
	elapsed := time.Since(start)
	r.GreaterOrEqual(elapsed, deployTimeout)
	r.Less(elapsed, deployTimeout+50*time.Millisecond)
}
func TestIntegrationWaitReplicas(t *testing.T) {
	const (
		deployTimeout   = 2 * time.Second
		responseTimeout = 1 * time.Second
		deployName      = "testdeployment"
	)
	ctx := context.Background()
	r := require.New(t)
	h, err := newHarness(deployTimeout, responseTimeout)
	r.NoError(err)
	originPort, err := strconv.Atoi(h.originURL.Port())
	r.NoError(err)
	h.routingTable.AddTarget(
		hostForTest(t),
		targetFromURL(
			h.originURL,
			originPort,
			deployName,
			123,
		),
	)
	initialDeployment := appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{Name: deployName},
		Spec: appsv1.DeploymentSpec{
			Replicas: i32Ptr(0),
		},
	}
	h.deplCache.Set(deployName, initialDeployment)
	watcher := h.deplCache.SetWatcher(deployName)
	var response *http.Response
	grp, _ := errgroup.WithContext(ctx)
	grp.Go(func() error {
		resp, err := doRequest(
			http.DefaultClient,
			"GET",
			h.proxyURL.String(),
			hostForTest(t),
			nil,
		)
		if err != nil {
			return err
		}
		response = resp
		return nil
	})
	const sleepDur = deployTimeout / 4
	grp.Go(func() error {
		t.Logf("Sleeping for %s", sleepDur)
		time.Sleep(sleepDur)
		t.Logf("Woke up, setting replicas to 10")
		modifiedDeployment := initialDeployment.DeepCopy()
		modifiedDeployment.Spec.Replicas = i32Ptr(10)
		modifiedDeployment.Status.ReadyReplicas = 10
		watcher.Modify(modifiedDeployment)
		return nil
	})
	start := time.Now()
	r.NoError(grp.Wait())
	elapsed := time.Since(start)
	a := assert.New(t)
	a.GreaterOrEqual(elapsed, sleepDur)
	a.Less(
		elapsed,
		sleepDur*2,
		"the handler took too long. this is usually because it timed out, not because it didn't find the watch event in time",
	)
	a.Equal(200, response.StatusCode)
}
func doRequest(
	cl *http.Client,
	method,
	urlStr,
	host string,
	body io.ReadCloser,
) (*http.Response, error) {
	req, err := http.NewRequest("GET", urlStr, nil)
	if err != nil {
		return nil, err
	}
	req.Host = host
	res, err := cl.Do(req)
	if err != nil {
		return nil, err
	}
	return res, nil
}
type harness struct {
	lggr         logr.Logger
	proxyHdl     http.Handler
	proxySrv     *httptest.Server
	proxyURL     *url.URL
	originHdl    http.Handler
	originSrv    *httptest.Server
	originURL    *url.URL
	routingTable *routing.Table
	dialCtxFunc  kedanet.DialContextFunc
	deplCache    *k8s.FakeDeploymentCache
	waitFunc     forwardWaitFunc
}
func newHarness(
	deployReplicasTimeout,
	responseHeaderTimeout time.Duration,
) (*harness, error) {
	lggr := logr.Discard()
	routingTable := routing.NewTable()
	dialContextFunc := kedanet.DialContextWithRetry(
		&net.Dialer{
			Timeout: 2 * time.Second,
		},
		wait.Backoff{
			Steps:    2,
			Duration: time.Second,
		},
	)
	deplCache := k8s.NewFakeDeploymentCache()
	waitFunc := newDeployReplicasForwardWaitFunc(deplCache)
	proxyHdl := newForwardingHandler(
		lggr,
		routingTable,
		dialContextFunc,
		waitFunc,
		forwardingConfig{
			waitTimeout:       deployReplicasTimeout,
			respHeaderTimeout: responseHeaderTimeout,
		},
	)
	originHdl := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("hello!"))
	})
	testOriginSrv, originSrvURL, err := kedanet.StartTestServer(originHdl)
	if err != nil {
		return nil, err
	}
	proxySrv, proxySrvURL, err := kedanet.StartTestServer(proxyHdl)
	if err != nil {
		return nil, err
	}
	return &harness{
		lggr:         lggr,
		proxyHdl:     proxyHdl,
		proxySrv:     proxySrv,
		proxyURL:     proxySrvURL,
		originHdl:    originHdl,
		originSrv:    testOriginSrv,
		originURL:    originSrvURL,
		routingTable: routingTable,
		dialCtxFunc:  dialContextFunc,
		deplCache:    deplCache,
		waitFunc:     waitFunc,
	}, nil
}
func (h *harness) close() {
	h.proxySrv.Close()
	h.originSrv.Close()
}
func (h *harness) String() string {
	return fmt.Sprintf(
		"harness{proxy: %s, origin: %s}",
		h.proxyURL.String(),
		h.originURL.String(),
	)
}
func i32Ptr(i int32) *int32 {
	return &i
}
func hostForTest(t *testing.T) string {
	t.Helper()
	return fmt.Sprintf("%s.integrationtest.interceptor.kedahttp.dev", t.Name())
}