Ctrl K
ComunidadGuías…

pkg/k8score/node_debug_test.go

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

Archivo original ↗Preguntar a KiBot
package k8score

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

func TestCreateNodeDebugPodIdentity(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost || r.URL.Path != "/api/v1/namespaces/default/pods" {
			t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
		}
		var pod corev1.Pod
		if err := json.NewDecoder(r.Body).Decode(&pod); err != nil {
			t.Error(err)
		}
		if pod.Spec.NodeName != "node-a" || pod.Spec.ActiveDeadlineSeconds == nil || *pod.Spec.ActiveDeadlineSeconds != 3600 {
			t.Errorf("unexpected pod spec: %+v", pod.Spec)
		}
		pod.UID = types.UID("created-uid")
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(pod)
	}))
	defer server.Close()
	client, err := kubernetes.NewForConfig(&rest.Config{Host: server.URL, ContentConfig: rest.ContentConfig{ContentType: "application/json"}})
	if err != nil {
		t.Fatal(err)
	}
	result, err := CreateNodeDebugPod(context.Background(), client, "node-a", "")
	if err != nil {
		t.Fatal(err)
	}
	if result.UID != "created-uid" || result.Namespace != "default" || result.PodName == "" || result.ContainerName != "debug" {
		t.Fatalf("unexpected result: %+v", result)
	}
	data, err := json.Marshal(result)
	if err != nil {
		t.Fatal(err)
	}
	var decoded map[string]any
	if err := json.Unmarshal(data, &decoded); err != nil {
		t.Fatal(err)
	}
	if decoded["uid"] != "created-uid" {
		t.Fatalf("UID missing from JSON: %s", data)
	}
}

func TestDeleteNodeDebugPod(t *testing.T) {
	for _, tc := range []struct {
		name   string
		status int
		reason metav1.StatusReason
	}{
		{"success", http.StatusOK, ""},
		{"already gone", http.StatusNotFound, metav1.StatusReasonNotFound},
		{"UID conflict", http.StatusConflict, metav1.StatusReasonConflict},
		{"forbidden", http.StatusForbidden, metav1.StatusReasonForbidden},
	} {
		t.Run(tc.name, func(t *testing.T) {
			requests := 0
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				requests++
				if r.Method != http.MethodDelete || r.URL.Path != "/api/v1/namespaces/default/pods/session-a" || r.URL.RawQuery != "" {
					t.Errorf("must delete exact pod, not a collection: %s %s", r.Method, r.URL)
				}
				var options metav1.DeleteOptions
				if err := json.NewDecoder(r.Body).Decode(&options); err != nil {
					t.Error(err)
				}
				if options.Preconditions == nil || options.Preconditions.UID == nil || *options.Preconditions.UID != "fdde0bca-d4df-4263-bf04-88ce73cc90c1" {
					t.Errorf("missing UID precondition: %+v", options)
				}
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(tc.status)
				json.NewEncoder(w).Encode(metav1.Status{Status: "Failure", Reason: tc.reason, Code: int32(tc.status), Message: "test response"})
			}))
			defer server.Close()
			client, err := kubernetes.NewForConfig(&rest.Config{Host: server.URL, ContentConfig: rest.ContentConfig{ContentType: "application/json"}})
			if err != nil {
				t.Fatal(err)
			}
			err = DeleteNodeDebugPod(context.Background(), client, "default", "session-a", "fdde0bca-d4df-4263-bf04-88ce73cc90c1")
			switch tc.status {
			case http.StatusOK, http.StatusNotFound:
				if err != nil {
					t.Fatal(err)
				}
			case http.StatusConflict:
				if !apierrors.IsConflict(err) {
					t.Fatalf("expected conflict, got %v", err)
				}
			case http.StatusForbidden:
				if !apierrors.IsForbidden(err) {
					t.Fatalf("expected forbidden, got %v", err)
				}
			}
			if requests != 1 {
				t.Fatalf("expected exactly one guarded request, got %d", requests)
			}
		})
	}
}

func TestDeleteNodeDebugPodRequiresIdentity(t *testing.T) {
	for i, identity := range [][3]string{{"", "pod", "uid"}, {"default", "", "uid"}, {"default", "pod", ""}} {
		t.Run(fmt.Sprint(i), func(t *testing.T) {
			// A nil client ensures validation cannot issue a delete or fall back to a collection.
			if err := DeleteNodeDebugPod(context.Background(), nil, identity[0], identity[1], types.UID(identity[2])); err == nil || err.Error() != "debug pod namespace, name and UID are required" {
				t.Fatalf("expected identity validation, got %v", err)
			}
		})
	}
}

func TestDeleteNodeDebugPodRejectsMalformedIdentity(t *testing.T) {
	const uid = "fdde0bca-d4df-4263-bf04-88ce73cc90c1"
	for _, tc := range []struct{ name, namespace, podName, uid string }{
		{"namespace whitespace", " default", "pod", uid},
		{"namespace uppercase", "Default", "pod", uid},
		{"namespace dotted", "team.a", "pod", uid},
		{"namespace too long", strings.Repeat("a", 64), "pod", uid},
		{"pod slash", "default", "a/b", uid},
		{"pod percent", "default", "pod%2Fa", uid},
		{"pod uppercase", "default", "Pod", uid},
		{"pod too long", "default", strings.Repeat("a", 254), uid},
		{"UID whitespace", "default", "pod", " " + uid},
		{"UID malformed", "default", "pod", "not-a-uuid"},
		{"UID invalid hex", "default", "pod", "zzzzzzzz-d4df-4263-bf04-88ce73cc90c1"},
		{"UID URN", "default", "pod", "urn:uuid:" + uid},
		{"UID braces", "default", "pod", "{" + uid + "}"},
		{"UID no hyphens", "default", "pod", strings.ReplaceAll(uid, "-", "")},
	} {
		t.Run(tc.name, func(t *testing.T) {
			requests := 0
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requests++; w.WriteHeader(http.StatusOK) }))
			defer server.Close()
			client, err := kubernetes.NewForConfig(&rest.Config{Host: server.URL})
			if err != nil {
				t.Fatal(err)
			}
			err = DeleteNodeDebugPod(context.Background(), client, tc.namespace, tc.podName, types.UID(tc.uid))
			if err == nil {
				t.Fatal("expected invalid identity to be rejected")
			}
			if requests != 0 {
				t.Fatalf("invalid identity issued %d Kubernetes requests", requests)
			}
		})
	}
}

func TestValidateNodeDebugPodIdentityAcceptsKubernetesNames(t *testing.T) {
	for _, tc := range []struct{ namespace, podName string }{
		{"default", "radar-node-debug-worker-1-123"},
		{"team-a", "debug.worker-1"},
		{strings.Repeat("a", 63), strings.Repeat("a", 253)},
	} {
		if err := ValidateNodeDebugPodIdentity(tc.namespace, tc.podName, "fdde0bca-d4df-4263-bf04-88ce73cc90c1"); err != nil {
			t.Fatalf("valid namespace/name rejected: %v", err)
		}
	}
}