Ctrl K
ComunidadGuías…

internal/prom/client_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package prom

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/kubernetes"
	k8sfake "k8s.io/client-go/kubernetes/fake"
	"k8s.io/client-go/rest"
	k8stesting "k8s.io/client-go/testing"
)

func service(namespace, name string, labels map[string]string, ports ...corev1.ServicePort) *corev1.Service {
	return &corev1.Service{
		Namespace: namespace, Name: name, Labels: labels,
		Spec: corev1.ServiceSpec{Ports: ports},
	}
}

func TestParseTarget(t *testing.T) {
	cases := []struct {
		spec string
		want Target
		bad  bool
	}{
		{spec: "", want: Target{}},
		{spec: "monitoring/prom:9090", want: Target{Namespace: "monitoring", Service: "prom", Port: "9090"}},
		{spec: "monitoring/prom", want: Target{Namespace: "monitoring", Service: "prom", Port: "9090"}},
		{spec: "noslash", bad: true},
		{spec: "/prom:9090", bad: true},
		{spec: "monitoring/:9090", bad: true},
		{spec: "monitoring/prom:", bad: true},
		{spec: "monitoring/prom:9090:extra", bad: true},
		{spec: "monitoring/prom:web", bad: true},
		{spec: "monitoring/prom:0", bad: true},
		{spec: "monitoring/prom:65536", bad: true},
		{spec: "monitoring/prom:65535", want: Target{Namespace: "monitoring", Service: "prom", Port: "65535"}},
	}
	for _, tc := range cases {
		t.Run(tc.spec, func(t *testing.T) {
			got, err := ParseTarget(tc.spec)
			if tc.bad {
				if err == nil {
					t.Fatalf("expected %q to be refused", tc.spec)
				}
				return
			}
			if err != nil {
				t.Fatalf("parse: %v", err)
			}
			if got != tc.want {
				t.Fatalf("got %+v, want %+v", got, tc.want)
			}
		})
	}
}

func TestWebPortPrefers9090(t *testing.T) {
	ports := []corev1.ServicePort{{Name: "reloader-web", Port: 8080}, {Name: "http-web", Port: 9090}}
	if got := webPort(ports); got != "9090" {
		t.Fatalf("webPort = %q, want 9090", got)
	}
}

func TestWebPortFallsBackToANamedWebPort(t *testing.T) {
	ports := []corev1.ServicePort{{Name: "grpc", Port: 1234}, {Name: "http-web", Port: 9999}}
	if got := webPort(ports); got != "9999" {
		t.Fatalf("webPort = %q, want 9999", got)
	}
}

func TestWebPortRejectsAServiceWithNoWebPort(t *testing.T) {
	if got := webPort([]corev1.ServicePort{{Name: "grpc", Port: 1234}}); got != "" {
		t.Fatalf("webPort = %q, want empty", got)
	}
}

func TestDiscoverPrefersTheOperatorLabel(t *testing.T) {
	cs := k8sfake.NewClientset(
		service("other", "some-prometheus", map[string]string{"app.kubernetes.io/name": "prometheus"},
			corev1.ServicePort{Name: "http-web", Port: 9090}),
		service("monitoring", "prometheus-operated", map[string]string{"operated-prometheus": "true"},
			corev1.ServicePort{Name: "http-web", Port: 9090}),
	)
	client := NewClient(cs, Target{})

	got, err := client.discover(context.Background())
	if err != nil {
		t.Fatalf("discover: %v", err)
	}
	if got.Service != "prometheus-operated" {
		t.Fatalf("service = %q, want the operator-managed one", got.Service)
	}
}

func TestDiscoverFallsBackToTheChartLabel(t *testing.T) {
	cs := k8sfake.NewClientset(
		service("obs", "prom", map[string]string{"app.kubernetes.io/name": "prometheus"},
			corev1.ServicePort{Name: "http-web", Port: 9090}),
	)
	client := NewClient(cs, Target{})

	got, err := client.discover(context.Background())
	if err != nil {
		t.Fatalf("discover: %v", err)
	}
	if got.Namespace != "obs" || got.Service != "prom" {
		t.Fatalf("got %+v", got)
	}
}

func TestDiscoverSkipsAServiceWithoutAWebPort(t *testing.T) {
	cs := k8sfake.NewClientset(
		service("monitoring", "prometheus-operated", map[string]string{"operated-prometheus": "true"},
			corev1.ServicePort{Name: "grpc", Port: 1234}),
	)
	client := NewClient(cs, Target{})

	_, err := client.discover(context.Background())
	if err == nil {
		t.Fatal("expected a refusal")
	}
	if !strings.Contains(err.Error(), "--prometheus") {
		t.Fatalf("message = %q, want it to name the flag", err.Error())
	}
}

func TestDiscoverRefusesToPickBetweenTwoCandidates(t *testing.T) {
	cs := k8sfake.NewClientset(
		service("monitoring", "prometheus-operated", map[string]string{"operated-prometheus": "true"},
			corev1.ServicePort{Name: "http-web", Port: 9090}),
		service("tenant-a", "prometheus-operated", map[string]string{"operated-prometheus": "true"},
			corev1.ServicePort{Name: "http-web", Port: 9090}),
	)
	client := NewClient(cs, Target{})

	_, err := client.discover(context.Background())

	if err == nil {
		t.Fatal("a second labeled service was picked up silently")
	}
	if !strings.Contains(err.Error(), "tenant-a/prometheus-operated") {
		t.Fatalf("message = %q, want both candidates named", err.Error())
	}
	if !strings.Contains(err.Error(), "--prometheus") {
		t.Fatalf("message = %q, want it to name the flag", err.Error())
	}
}

func TestDiscoverNamesOnlyTheFirstFewCandidates(t *testing.T) {
	objs := make([]runtime.Object, 0, maxNamed+2)
	for i := range maxNamed + 2 {
		objs = append(objs, service(fmt.Sprintf("tenant-%d", i), "prom", map[string]string{"operated-prometheus": "true"},
			corev1.ServicePort{Name: "http-web", Port: 9090}))
	}
	client := NewClient(k8sfake.NewClientset(objs...), Target{})

	_, err := client.discover(context.Background())

	if err == nil {
		t.Fatal("expected a refusal")
	}
	if !strings.Contains(err.Error(), "...") {
		t.Fatalf("message = %q, want the list cut short", err.Error())
	}
}

func TestDiscoverStillTakesASingleMatchWithAnUnusableSibling(t *testing.T) {
	cs := k8sfake.NewClientset(
		service("monitoring", "prometheus-operated", map[string]string{"operated-prometheus": "true"},
			corev1.ServicePort{Name: "http-web", Port: 9090}),
		service("monitoring", "prometheus-grpc", map[string]string{"operated-prometheus": "true"},
			corev1.ServicePort{Name: "grpc", Port: 1234}),
	)
	client := NewClient(cs, Target{})

	got, err := client.discover(context.Background())
	if err != nil {
		t.Fatalf("discover: %v", err)
	}

	if got.Service != "prometheus-operated" {
		t.Fatalf("service = %q", got.Service)
	}
}

func TestDiscoverHonoursTheOverride(t *testing.T) {
	client := NewClient(k8sfake.NewClientset(), Target{Namespace: "x", Service: "y", Port: "1"})

	got, err := client.discover(context.Background())
	if err != nil {
		t.Fatalf("discover: %v", err)
	}
	if got.Service != "y" {
		t.Fatalf("override ignored: %+v", got)
	}
}

func TestTargetString(t *testing.T) {
	target := Target{Namespace: "monitoring", Service: "prom", Port: "9090", Scheme: "https"}
	if got := target.String(); got != "monitoring/prom:9090 (https)" {
		t.Fatalf("String = %q", got)
	}
}

const sample = `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{},"values":[[1785434552,"0.028"],[1785434612,"0.031"]]}]}}`

func TestDecodeRange(t *testing.T) {
	points, err := decodeRange([]byte(sample))
	if err != nil {
		t.Fatalf("decode: %v", err)
	}
	if len(points) != 2 {
		t.Fatalf("points = %d", len(points))
	}
	if points[0].At != 1785434552 || points[0].Value != 0.028 {
		t.Fatalf("first = %+v", points[0])
	}
}

func TestDecodeRangeEmptyResult(t *testing.T) {
	points, err := decodeRange([]byte(`{"status":"success","data":{"result":[]}}`))
	if err != nil {
		t.Fatalf("decode: %v", err)
	}
	if len(points) != 0 {
		t.Fatalf("points = %d, want none", len(points))
	}
}

func TestDecodeRangeRefusesToAnswerForOneOfManySeries(t *testing.T) {
	twoSeries := `{"status":"success","data":{"result":[
		{"metric":{"pod":"a"},"values":[[1785434552,"0.028"]]},
		{"metric":{"pod":"b"},"values":[[1785434552,"9.999"]]}
	]}}`

	_, err := decodeRange([]byte(twoSeries))

	if err == nil {
		t.Fatal("two series came back and the range answered as though there were one")
	}
	if !strings.Contains(err.Error(), "aggregate") {
		t.Fatalf("error = %q, want it to say how to fix the query", err)
	}
}

func TestDecodeRangeStillAnswersForASingleSeries(t *testing.T) {
	oneSeries := `{"status":"success","data":{"result":[
		{"metric":{"pod":"a"},"values":[[1785434552,"0.028"],[1785434612,"0.031"]]}
	]}}`

	points, err := decodeRange([]byte(oneSeries))
	if err != nil {
		t.Fatalf("decode: %v", err)
	}
	if len(points) != 2 {
		t.Fatalf("points = %d, want both", len(points))
	}
}

func TestTheQueriesThatUseRangeAggregateToOneSeries(t *testing.T) {
	for name, query := range map[string]string{"cpu": cpuQuery, "memory": memoryQuery} {
		t.Run(name, func(t *testing.T) {
			if !strings.HasPrefix(query, "sum(") {
				t.Fatalf(
					"%s does not aggregate, so a pod with several containers would make "+
						"the range refuse: %s", name, query,
				)
			}
		})
	}
}

func TestDecodeRangeRejectsAnError(t *testing.T) {
	_, err := decodeRange([]byte(`{"status":"error","error":"parse error"}`))
	if err == nil {
		t.Fatal("expected an error")
	}
	if !strings.Contains(err.Error(), "parse error") {
		t.Fatalf("message = %q", err.Error())
	}
}

func TestDecodeRangeRejectsGarbage(t *testing.T) {
	_, err := decodeRange([]byte("not json"))
	if err == nil {
		t.Fatal("expected an error")
	}
}

func TestDecodeRangeSkipsMalformedPoints(t *testing.T) {
	raw := `{"status":"success","data":{"result":[{"values":[
	  [1,"1.0"], [2], ["nan","1.0"], [3,4], [5,"notanumber"], [6,"2.0"]
	]}]}}`
	points, err := decodeRange([]byte(raw))
	if err != nil {
		t.Fatalf("decode: %v", err)
	}
	if len(points) != 2 {
		t.Fatalf("points = %+v, want only the two well-formed pairs", points)
	}
}

func TestDecodeRangeSkipsNonfinitePoints(t *testing.T) {
	raw := `{"status":"success","data":{"result":[{"values":[
	  [1,"NaN"], [2,"+Inf"], [3,"-Inf"], [4,"2.0"]
	]}]}}`

	points, err := decodeRange([]byte(raw))
	if err != nil {
		t.Fatalf("decode: %v", err)
	}
	if len(points) != 1 || points[0].Value != 2 {
		t.Fatalf("points = %+v, want only the finite sample", points)
	}
}

func TestParseSpan(t *testing.T) {
	got, err := ParseSpan("")
	if err != nil || got != DefaultSpan {
		t.Fatalf("empty = %v, %v", got, err)
	}
	got, err = ParseSpan("15m")
	if err != nil || got != 15*time.Minute {
		t.Fatalf("15m = %v, %v", got, err)
	}
	for _, bad := range []string{"banana", "-1h", "0s", "48h"} {
		if _, err := ParseSpan(bad); err == nil {
			t.Fatalf("%q should be refused", bad)
		}
	}
}

func TestStepForKeepsThePointCountBounded(t *testing.T) {
	if got := StepFor(time.Hour); got != 15*time.Second {
		t.Fatalf("1h step = %v, want the floor", got)
	}
	if got := StepFor(24 * time.Hour); got != 6*time.Minute {
		t.Fatalf("24h step = %v", got)
	}
	if points := int(24 * time.Hour / StepFor(24*time.Hour)); points > maxPoints {
		t.Fatalf("24h yields %d points, over the cap", points)
	}
}

type stubProxy struct {
	calls         []Target
	paths         []string
	params        []map[string]string
	failFor       map[string]bool
	forbidden     bool
	body          string
	rangeErr      error
	rangeErrAfter int
}

func (s *stubProxy) Get(_ context.Context, target Target, path string, params map[string]string) ([]byte, error) {
	s.calls = append(s.calls, target)
	s.paths = append(s.paths, path)
	s.params = append(s.params, params)
	if s.forbidden {
		return nil, apierrors.NewForbidden(
			schema.GroupResource{Resource: "services"},
			target.Service,
			errors.New("requires container.services.proxy"),
		)
	}
	if s.failFor[target.Scheme] {
		return nil, errors.New("connection refused over " + target.Scheme)
	}
	if path == rangePath && s.rangeErr != nil {
		if s.rangeErrAfter == 0 {
			return nil, s.rangeErr
		}
		s.rangeErrAfter--
	}
	if path == rangePath {
		return []byte(s.body), nil
	}
	return []byte(`{"status":"success"}`), nil
}

func operatedClient(t *testing.T, proxy Proxy) *Client {
	t.Helper()
	cs := k8sfake.NewClientset(
		service("monitoring", "prometheus-operated", map[string]string{"operated-prometheus": "true"},
			corev1.ServicePort{Name: "http-web", Port: 9090}),
	)
	return NewClientWithProxy(cs, proxy, Target{})
}

func TestTargetProbesHTTPSFirst(t *testing.T) {
	proxy := &stubProxy{}
	client := operatedClient(t, proxy)

	target, err := client.Target(context.Background())
	if err != nil {
		t.Fatalf("target: %v", err)
	}
	if target.Scheme != "https" {
		t.Fatalf("scheme = %q, want https tried first", target.Scheme)
	}
	if len(proxy.calls) != 1 {
		t.Fatalf("probed %d times, want 1", len(proxy.calls))
	}
}

func TestTargetUsesTheOverrideWithoutListingServices(t *testing.T) {
	proxy := &stubProxy{}
	client := NewClientWithProxy(
		k8sfake.NewClientset(),
		proxy,
		Target{Namespace: "obs", Service: "prom", Port: "9091"},
	)

	target, err := client.Target(context.Background())
	if err != nil {
		t.Fatalf("target: %v", err)
	}
	if target.String() != "obs/prom:9091 (https)" {
		t.Fatalf("target = %q, want the override probed", target.String())
	}
}

func TestTargetFallsBackToHTTP(t *testing.T) {
	proxy := &stubProxy{failFor: map[string]bool{"https": true}}
	client := operatedClient(t, proxy)

	target, err := client.Target(context.Background())
	if err != nil {
		t.Fatalf("target: %v", err)
	}
	if target.Scheme != "http" {
		t.Fatalf("scheme = %q, want the http fallback", target.Scheme)
	}
}

func TestTargetReportsAForbiddenProxyOnce(t *testing.T) {
	proxy := &stubProxy{forbidden: true}
	client := operatedClient(t, proxy)

	_, err := client.Target(context.Background())
	if err == nil {
		t.Fatal("expected an error")
	}
	if !errors.Is(err, ErrUnavailable) {
		t.Fatalf("err = %v, want ErrUnavailable", err)
	}
	if !strings.Contains(err.Error(), "services/proxy") {
		t.Fatalf("message = %q, want the missing permission named", err.Error())
	}
	if strings.Contains(err.Error(), "did not answer") {
		t.Fatalf("message = %q, a denial is not a connectivity failure", err.Error())
	}
	if len(proxy.calls) != 1 {
		t.Fatalf("probed %d times, want no second scheme after a denial", len(proxy.calls))
	}
}

func TestTargetReportsWhenNeitherSchemeAnswers(t *testing.T) {
	proxy := &stubProxy{failFor: map[string]bool{"https": true, "http": true}}
	client := operatedClient(t, proxy)

	_, err := client.Target(context.Background())
	if err == nil {
		t.Fatal("expected an error")
	}
	if !errors.Is(err, ErrUnavailable) {
		t.Fatalf("err = %v, want ErrUnavailable", err)
	}
}

func TestTargetIsResolvedOnce(t *testing.T) {
	proxy := &stubProxy{}
	client := operatedClient(t, proxy)

	first, _ := client.Target(context.Background())
	second, err := client.Target(context.Background())
	if err != nil {
		t.Fatalf("target: %v", err)
	}
	if first != second {
		t.Fatalf("%+v != %+v", first, second)
	}
	if len(proxy.calls) != 1 {
		t.Fatalf("probed %d times, want the result cached", len(proxy.calls))
	}
}

func TestTargetReportsADiscoveryFailure(t *testing.T) {
	client := NewClientWithProxy(k8sfake.NewClientset(), &stubProxy{}, Target{})

	_, err := client.Target(context.Background())
	if err == nil {
		t.Fatal("expected an error")
	}
}

func TestRangeSendsTheQueryWindow(t *testing.T) {
	proxy := &stubProxy{body: sample}
	client := operatedClient(t, proxy)
	start := time.Unix(1000, 0)
	end := time.Unix(4600, 0)

	points, err := client.Range(context.Background(), "up", start, end, 30*time.Second)
	if err != nil {
		t.Fatalf("range: %v", err)
	}
	if len(points) != 2 {
		t.Fatalf("points = %d", len(points))
	}
	sent := proxy.params[len(proxy.params)-1]
	if sent["query"] != "up" || sent["start"] != "1000" || sent["end"] != "4600" || sent["step"] != "30" {
		t.Fatalf("params = %+v", sent)
	}
}

func TestRangeSurfacesAProxyFailure(t *testing.T) {
	proxy := &stubProxy{rangeErr: errors.New("service unavailable")}
	client := operatedClient(t, proxy)

	_, err := client.Range(context.Background(), "up", time.Unix(0, 0), time.Unix(60, 0), time.Second)
	if err == nil {
		t.Fatal("expected an error")
	}
}

func TestRangeReportsAnUnresolvableTarget(t *testing.T) {
	client := NewClientWithProxy(k8sfake.NewClientset(), &stubProxy{}, Target{})

	_, err := client.Range(context.Background(), "up", time.Unix(0, 0), time.Unix(60, 0), time.Second)
	if err == nil {
		t.Fatal("expected an error")
	}
}

func TestPodHistoryQueriesCPUAndMemory(t *testing.T) {
	proxy := &stubProxy{body: sample}
	client := operatedClient(t, proxy)

	history, err := client.PodHistory(context.Background(), "monitoring", "loki-0", time.Hour, time.Unix(10000, 0))
	if err != nil {
		t.Fatalf("history: %v", err)
	}
	if history.Namespace != "monitoring" || history.Pod != "loki-0" {
		t.Fatalf("history = %+v", history)
	}
	if len(history.CPU) != 2 || len(history.Memory) != 2 {
		t.Fatalf("cpu %d memory %d", len(history.CPU), len(history.Memory))
	}
	if history.Source != "monitoring/prometheus-operated:9090 (https)" {
		t.Fatalf("source = %q", history.Source)
	}
	queries := []string{}
	for _, sent := range proxy.params {
		if sent != nil {
			queries = append(queries, sent["query"])
		}
	}
	if len(queries) != 2 {
		t.Fatalf("queries = %v", queries)
	}
	if !strings.Contains(queries[0], "container_cpu_usage_seconds_total") {
		t.Fatalf("first query = %q", queries[0])
	}
	if !strings.Contains(queries[1], "container_memory_working_set_bytes") {
		t.Fatalf("second query = %q", queries[1])
	}
	for _, q := range queries {
		if !strings.Contains(q, `container!=""`) {
			t.Fatalf("query %q must exclude the pod-level cgroup or it double counts", q)
		}
	}
}

func TestPodHistoryReportsAnUnresolvableTarget(t *testing.T) {
	client := NewClientWithProxy(k8sfake.NewClientset(), &stubProxy{}, Target{})

	_, err := client.PodHistory(context.Background(), "ns", "pod", time.Hour, time.Unix(0, 0))
	if err == nil {
		t.Fatal("expected an error")
	}
}

func TestPodHistorySurfacesACPUQueryFailure(t *testing.T) {
	proxy := &stubProxy{rangeErr: errors.New("boom")}
	client := operatedClient(t, proxy)

	_, err := client.PodHistory(context.Background(), "ns", "pod", time.Hour, time.Unix(0, 0))
	if err == nil {
		t.Fatal("expected an error")
	}
}

func TestPodHistorySurfacesAMemoryQueryFailure(t *testing.T) {
	proxy := &stubProxy{
		body:          sample,
		rangeErr:      errors.New("boom"),
		rangeErrAfter: 1,
	}
	client := operatedClient(t, proxy)

	_, err := client.PodHistory(context.Background(), "ns", "pod", time.Hour, time.Unix(0, 0))
	if err == nil {
		t.Fatal("expected an error")
	}
}

func TestRangeForgetsATargetThatStopsAnswering(t *testing.T) {
	proxy := &stubProxy{}
	client := operatedClient(t, proxy)
	_, err := client.Target(context.Background())
	if err != nil {
		t.Fatalf("target: %v", err)
	}

	proxy.rangeErr = errors.New("connection refused")
	_, rangeErr := client.Range(context.Background(), "up", time.Unix(0, 0), time.Unix(60, 0), time.Minute)
	if rangeErr == nil {
		t.Fatal("expected the range query to fail")
	}

	client.mu.Lock()
	cached := client.resolved
	client.mu.Unlock()
	if cached != nil {
		t.Fatalf("target %+v stayed cached after the endpoint stopped answering", cached)
	}
}

func TestRangeKeepsATargetThatAnswers(t *testing.T) {
	proxy := &stubProxy{body: `{"status":"success","data":{"result":[]}}`}
	client := operatedClient(t, proxy)
	_, err := client.Target(context.Background())
	if err != nil {
		t.Fatalf("target: %v", err)
	}

	_, rangeErr := client.Range(context.Background(), "up", time.Unix(0, 0), time.Unix(60, 0), time.Minute)
	if rangeErr != nil {
		t.Fatalf("range: %v", rangeErr)
	}

	client.mu.Lock()
	cached := client.resolved
	client.mu.Unlock()
	if cached == nil {
		t.Fatal("a working target was dropped from the cache")
	}
}

func TestForgetKeepsATargetThatWasAlreadyReplaced(t *testing.T) {
	client := &Client{resolved: &Target{Namespace: "monitoring", Service: "new", Port: "9090"}}

	client.forget(Target{Namespace: "monitoring", Service: "old", Port: "9090"})

	if client.resolved == nil {
		t.Fatal("forget dropped a target it was not asked about")
	}
}

func TestForgetIsSafeWithNothingCached(t *testing.T) {
	client := &Client{}

	client.forget(Target{Namespace: "monitoring", Service: "prom", Port: "9090"})

	if client.resolved != nil {
		t.Fatal("forget invented a target")
	}
}

func TestServiceProxyAsksTheApiserverToProxy(t *testing.T) {
	var asked string
	apiserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		asked = r.URL.RequestURI()
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"status":"success"}`))
	}))
	t.Cleanup(apiserver.Close)
	cs, err := kubernetes.NewForConfig(&rest.Config{Host: apiserver.URL})
	if err != nil {
		t.Fatalf("clientset: %v", err)
	}
	proxy := &serviceProxy{cs: cs}
	target := Target{Namespace: "monitoring", Service: "prometheus", Port: "9090", Scheme: "http"}

	body, getErr := proxy.Get(context.Background(), target, "/api/v1/query", map[string]string{"query": "up"})

	if getErr != nil {
		t.Fatalf("Get: %v", getErr)
	}
	if string(body) != `{"status":"success"}` {
		t.Fatalf("body = %q, want what the service answered", string(body))
	}
	if !strings.Contains(asked, "/namespaces/monitoring/services/http:prometheus:9090/proxy/api/v1/query") {
		t.Fatalf("path = %q, want the service proxy path", asked)
	}
	if !strings.Contains(asked, "query=up") {
		t.Fatalf("path = %q, want the query parameter passed through", asked)
	}
}

func TestServiceProxyRefusesAnOversizedPrometheusResponse(t *testing.T) {
	apiserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(strings.Repeat("x", maxResponseBytes+1)))
	}))
	t.Cleanup(apiserver.Close)
	cs, err := kubernetes.NewForConfig(&rest.Config{Host: apiserver.URL})
	if err != nil {
		t.Fatalf("clientset: %v", err)
	}
	proxy := &serviceProxy{cs: cs}
	target := Target{Namespace: "monitoring", Service: "prometheus", Port: "9090", Scheme: "http"}

	_, getErr := proxy.Get(context.Background(), target, rangePath, nil)

	if getErr == nil {
		t.Fatal("an oversized prometheus response was accepted")
	}
	if !strings.Contains(getErr.Error(), "larger") {
		t.Fatalf("error = %q, want the response limit named", getErr)
	}
}

func TestOnlyMatchReportsAFailedServiceList(t *testing.T) {
	cs := k8sfake.NewClientset()
	cs.PrependReactor("list", "services", func(k8stesting.Action) (bool, runtime.Object, error) {
		return true, nil, errors.New("services are forbidden")
	})
	client := NewClient(cs, Target{})

	_, err := client.onlyMatch(context.Background(), "app=prometheus")

	if err == nil {
		t.Fatal("onlyMatch returned nil error")
	}
	if !strings.Contains(err.Error(), "forbidden") {
		t.Fatalf("error = %q, want the list failure", err.Error())
	}
}