package cache

import (
	"sort"
	"testing"

	"github.com/argoproj/argo-cd/gitops-engine/pkg/utils/kube"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"sigs.k8s.io/yaml"

	"github.com/argoproj/argo-cd/v3/common"
	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
	"github.com/argoproj/argo-cd/v3/util/argo/normalizers"
	"github.com/argoproj/argo-cd/v3/util/errors"
)

func strToUnstructured(jsonStr string) *unstructured.Unstructured {
	obj := make(map[string]any)
	err := yaml.Unmarshal([]byte(jsonStr), &obj)
	errors.CheckError(err)
	return &unstructured.Unstructured{Object: obj}
}

var (
	testService = strToUnstructured(`
  apiVersion: v1
  kind: Service
  metadata:
    name: helm-guestbook
    namespace: default
    resourceVersion: "123"
    uid: "4"
  spec:
    selector:
      app: guestbook
    type: LoadBalancer
  status:
    loadBalancer:
      ingress:
      - hostname: localhost`)

	testLinkAnnotatedService = strToUnstructured(`
  apiVersion: v1
  kind: Service
  metadata:
    name: helm-guestbook
    namespace: default
    resourceVersion: "123"
    uid: "4"
    annotations:
      link.argocd.argoproj.io/external-link: http://my-grafana.example.com/pre-generated-link
  spec:
    selector:
      app: guestbook
    type: LoadBalancer
  status:
    loadBalancer:
      ingress:
      - hostname: localhost`)

	testMaliciousLinkAnnotatedService = strToUnstructured(`
  apiVersion: v1
  kind: Service
  metadata:
    name: helm-guestbook
    namespace: default
    resourceVersion: "123"
    uid: "4"
    annotations:
      link.argocd.argoproj.io/javascript: 'javascript:alert(1)'
      link.argocd.argoproj.io/data: 'data:text/html,<script>alert(1)</script>'
      link.argocd.argoproj.io/vbscript: 'vbscript:msgbox(1)'
      link.argocd.argoproj.io/titled-javascript: 'click me|javascript:alert(1)'
      link.argocd.argoproj.io/no-scheme: 'example.com/foo'
      link.argocd.argoproj.io/safe: 'http://my-grafana.example.com/pre-generated-link'
  spec:
    selector:
      app: guestbook
    type: LoadBalancer
  status:
    loadBalancer:
      ingress:
      - hostname: localhost`)

	testIngress = strToUnstructured(`
  apiVersion: extensions/v1beta1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
    uid: "4"
  spec:
    backend:
      serviceName: not-found-service
      servicePort: 443
    rules:
    - host: helm-guestbook.example.com
      http:
        paths:
        - backend:
            serviceName: helm-guestbook
            servicePort: 443
          path: /
        - backend:
            serviceName: helm-guestbook
            servicePort: https
          path: /
    tls:
    - host: helm-guestbook.example.com
    secretName: my-tls-secret
  status:
    loadBalancer:
      ingress:
      - ip: 107.178.210.11`)

	testLinkAnnotatedIngress = strToUnstructured(`
  apiVersion: extensions/v1beta1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
    uid: "4"
    annotations:
      link.argocd.argoproj.io/external-link: http://my-grafana.example.com/ingress-link
  spec:
    backend:
      serviceName: not-found-service
      servicePort: 443
    rules:
    - host: helm-guestbook.example.com
      http:
        paths:
        - backend:
            serviceName: helm-guestbook
            servicePort: 443
          path: /
        - backend:
            serviceName: helm-guestbook
            servicePort: https
          path: /
    tls:
    - host: helm-guestbook.example.com
    secretName: my-tls-secret
  status:
    loadBalancer:
      ingress:
      - ip: 107.178.210.11`)

	testIgnoreDefaultLinksIngress = strToUnstructured(`
  apiVersion: extensions/v1beta1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
    uid: "4"
    annotations:
      link.argocd.argoproj.io/external-link: http://my-grafana.example.com/ingress-link
      argocd.argoproj.io/ignore-default-links: "true"
  spec:
    backend:
      serviceName: not-found-service
      servicePort: 443
    rules:
    - host: helm-guestbook.example.com
      http:
        paths:
        - backend:
            serviceName: helm-guestbook
            servicePort: 443
          path: /
        - backend:
            serviceName: helm-guestbook
            servicePort: https
          path: /
    tls:
    - host: helm-guestbook.example.com
    secretName: my-tls-secret
  status:
    loadBalancer:
      ingress:
      - ip: 107.178.210.11`)

	testIngressWildCardPath = strToUnstructured(`
  apiVersion: extensions/v1beta1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
    uid: "4"
  spec:
    backend:
      serviceName: not-found-service
      servicePort: 443
    rules:
    - host: helm-guestbook.example.com
      http:
        paths:
        - backend:
            serviceName: helm-guestbook
            servicePort: 443
          path: /*
        - backend:
            serviceName: helm-guestbook
            servicePort: https
          path: /*
    tls:
    - host: helm-guestbook.example.com
    secretName: my-tls-secret
  status:
    loadBalancer:
      ingress:
      - ip: 107.178.210.11`)

	testIngressWithoutTLS = strToUnstructured(`
  apiVersion: extensions/v1beta1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
    uid: "4"
  spec:
    backend:
      serviceName: not-found-service
      servicePort: 443
    rules:
    - host: helm-guestbook.example.com
      http:
        paths:
        - backend:
            serviceName: helm-guestbook
            servicePort: 443
          path: /
        - backend:
            serviceName: helm-guestbook
            servicePort: https
          path: /
  status:
    loadBalancer:
      ingress:
      - ip: 107.178.210.11`)

	testIngressNetworkingV1 = strToUnstructured(`
  apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
    uid: "4"
  spec:
    backend:
      service:
        name: not-found-service
        port:
          number: 443
    rules:
    - host: helm-guestbook.example.com
      http:
        paths:
        - backend:
            service:
              name: helm-guestbook
              port:
                number: 443
          path: /
        - backend:
            service:
              name: helm-guestbook
              port:
                name: https
          path: /
    tls:
    - host: helm-guestbook.example.com
    secretName: my-tls-secret
  status:
    loadBalancer:
      ingress:
      - ip: 107.178.210.11`)

	testIstioVirtualService = strToUnstructured(`
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: hello-world
  namespace: demo
spec:
  http:
    - match:
        - uri:
            prefix: "/1"
      route:
        - destination:
            host: service_full.demo.svc.cluster.local
        - destination:
            host: service_namespace.namespace
    - match:
        - uri:
            prefix: "/2"
      route:
        - destination:
            host: service
`)

	testIstioServiceEntry = strToUnstructured(`
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: echo
spec:
  exportTo:
  - '*'
  hosts:
  - echo.internal
  location: MESH_INTERNAL
  ports:
  - name: http
    number: 80
    protocol: HTTP
    targetPort: 5678 
  resolution: DNS

  workloadSelector:
    labels:
      app.kubernetes.io/name: echo-2
`)
)

// These tests are equivalent to tests in ui/src/app/applications/components/utils.test.tsx. If you update tests here,
// please make sure to update the equivalent tests in the UI.
func TestGetPodInfo(t *testing.T) {
	t.Parallel()

	t.Run("TestGetPodInfo", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: helm-guestbook-pod
    namespace: default
    ownerReferences:
    - apiVersion: extensions/v1beta1
      kind: ReplicaSet
      name: helm-guestbook-rs
    resourceVersion: "123"
    labels:
      app: guestbook
  spec:
    nodeName: minikube
    containers:
    - image: bar
      resources:
        requests:
          memory: 128Mi
`)

		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/1"},
			{Name: common.PodRequestsCPU, Value: "0"}, // strings imported from common
			{Name: common.PodRequestsMEM, Value: "134217728000"},
		}, info.Info)
		assert.Equal(t, []string{"bar"}, info.Images)
		assert.Equal(t, &PodInfo{
			NodeName:         "minikube",
			ResourceRequests: corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("128Mi")},
		}, info.PodInfo)
		assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{Labels: map[string]string{"app": "guestbook"}}, info.NetworkingInfo)
	})

	t.Run("TestGetPodWithInitialContainerInfo", func(t *testing.T) {
		pod := strToUnstructured(`
  apiVersion: "v1"
  kind: "Pod"
  metadata: 
    labels: 
      app: "app-with-initial-container"
    name: "app-with-initial-container-5f46976fdb-vd6rv"
    namespace: "default"
    ownerReferences: 
    - apiVersion: "apps/v1"
      kind: "ReplicaSet"
      name: "app-with-initial-container-5f46976fdb"
  spec: 
    containers: 
    - image: "alpine:latest"
      imagePullPolicy: "Always"
      name: "app-with-initial-container"
    initContainers: 
    - image: "alpine:latest"
      imagePullPolicy: "Always"
      name: "app-with-initial-container-logshipper"
    nodeName: "minikube"
  status: 
    containerStatuses: 
    - image: "alpine:latest"
      name: "app-with-initial-container"
      ready: true
      restartCount: 0
      started: true
      state: 
        running: 
          startedAt: "2024-10-08T08:44:25Z"
    initContainerStatuses: 
    - image: "alpine:latest"
      name: "app-with-initial-container-logshipper"
      ready: true
      restartCount: 0
      started: false
      state: 
        terminated: 
          exitCode: 0
          reason: "Completed"
    phase: "Running"
`)

		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Running"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "1/1"},
			{Name: common.PodRequestsCPU, Value: "0"},
			{Name: common.PodRequestsMEM, Value: "0"},
		}, info.Info)
	})

	t.Run("TestGetPodWithInitialContainerInfoWithResources", func(t *testing.T) {
		pod := strToUnstructured(`
        apiVersion: "v1"
        kind: "Pod"
        metadata:
            labels:
                app: "app-with-initial-container"
            name: "app-with-initial-container-5f46976fdb-vd6rv"
            namespace: "default"
            ownerReferences:
            - apiVersion: "apps/v1"
              kind: "ReplicaSet"
              name: "app-with-initial-container-5f46976fdb"
        spec:
            containers:
            - image: "alpine:latest"
              imagePullPolicy: "Always"
              name: "app-with-initial-container"
              resources:
                requests:
                  cpu: "100m"
                  memory: "128Mi"
                limits:
                  cpu: "500m"
                  memory: "512Mi"
            initContainers:
            - image: "alpine:latest"
              imagePullPolicy: "Always"
              name: "app-with-initial-container-logshipper"
              resources:
                requests:
                  cpu: "50m"
                  memory: "64Mi"
                limits:
                  cpu: "250m"
                  memory: "256Mi"
            nodeName: "minikube"
        status:
            containerStatuses:
            - image: "alpine:latest"
              name: "app-with-initial-container"
              ready: true
              restartCount: 0
              started: true
              state:
                running:
                  startedAt: "2024-10-08T08:44:25Z"
            initContainerStatuses:
            - image: "alpine:latest"
              name: "app-with-initial-container-logshipper"
              ready: true
              restartCount: 0
              started: false
              state:
                terminated:
                  exitCode: 0
                  reason: "Completed"
            phase: "Running"
    `)

		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Running"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "1/1"},
			{Name: common.PodRequestsCPU, Value: "100"},
			{Name: common.PodRequestsMEM, Value: "134217728000"},
		}, info.Info)
	})
	t.Run("TestGetPodInfoWithSidecar", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    labels:
      app: app-with-sidecar
    name: app-with-sidecar-6664cc788c-lqlrp
    namespace: default
    ownerReferences:
      - apiVersion: apps/v1
        kind: ReplicaSet
        name: app-with-sidecar-6664cc788c
  spec:
    containers:
    - image: 'docker.m.daocloud.io/library/alpine:latest'
      imagePullPolicy: Always
      name: app-with-sidecar
    initContainers:
    - image: 'docker.m.daocloud.io/library/alpine:latest'
      imagePullPolicy: Always
      name: logshipper
      restartPolicy: Always
    nodeName: minikube
  status:
    containerStatuses:
    - image: 'docker.m.daocloud.io/library/alpine:latest'
      name: app-with-sidecar
      ready: true
      restartCount: 0
      started: true
      state:
        running:
          startedAt: '2024-10-08T08:39:43Z'
    initContainerStatuses:
    - image: 'docker.m.daocloud.io/library/alpine:latest'
      name: logshipper
      ready: true
      restartCount: 0
      started: true
      state:
        running:
          startedAt: '2024-10-08T08:39:40Z'
    phase: Running
`)

		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Running"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "2/2"},
			{Name: common.PodRequestsCPU, Value: "0"},
			{Name: common.PodRequestsMEM, Value: "0"},
		}, info.Info)
	})

	t.Run("TestGetPodInfoWithInitialContainer", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    generateName: myapp-long-exist-56b7d8794d-
    labels:
      app: myapp-long-exist
    name: myapp-long-exist-56b7d8794d-pbgrd
    namespace: linghao
    ownerReferences:
      - apiVersion: apps/v1
        kind: ReplicaSet
        name: myapp-long-exist-56b7d8794d
  spec:
    containers:
      - image: alpine:latest
        imagePullPolicy: Always
        name: myapp-long-exist
    initContainers:
      - image: alpine:latest
        imagePullPolicy: Always
        name: myapp-long-exist-logshipper
    nodeName: minikube
  status:
    containerStatuses:
      - image: alpine:latest
        name: myapp-long-exist
        ready: false
        restartCount: 0
        started: false
        state:
          waiting:
            reason: PodInitializing
    initContainerStatuses:
      - image: alpine:latest
        name: myapp-long-exist-logshipper
        ready: false
        restartCount: 0
        started: true
        state:
          running:
            startedAt: '2024-10-09T08:03:45Z'
    phase: Pending
    startTime: '2024-10-09T08:02:39Z'
`)

		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Init:0/1"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/1"},
			{Name: common.PodRequestsCPU, Value: "0"},
			{Name: common.PodRequestsMEM, Value: "0"},
		}, info.Info)
	})

	// Test pod has 2 restartable init containers, the first one running but not started.
	t.Run("TestGetPodInfoWithRestartableInitContainer", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test1
  spec:
    initContainers:
      - name: restartable-init-1
        restartPolicy: Always
      - name: restartable-init-2
        restartPolicy: Always
    containers:
      - name: container
    nodeName: minikube
  status:
    phase: Pending
    initContainerStatuses:
      - name: restartable-init-1
        ready: false
        restartCount: 3
        state:
          running: {}
        started: false
        lastTerminationState:
          terminated:
            finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
      - name: restartable-init-2
        ready: false
        state:
          waiting: {}
        started: false
    containerStatuses:
      - ready: false
        restartCount: 0
        state:
          waiting: {}
    conditions:
      - type: ContainersReady
        status: "False"
      - type: Initialized
        status: "False"
`)

		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Init:0/2"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/3"},
			{Name: "Restart Count", Value: "3"},
			{Name: common.PodRequestsCPU, Value: "0"},
			{Name: common.PodRequestsMEM, Value: "0"},
		}, info.Info)
	})

	// Test pod has 2 restartable init containers, the first one started and the second one running but not started.
	t.Run("TestGetPodInfoWithPartiallyStartedInitContainers", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test1
  spec:
    initContainers:
      - name: restartable-init-1
        restartPolicy: Always
      - name: restartable-init-2
        restartPolicy: Always
    containers:
      - name: container
    nodeName: minikube
  status:
    phase: Pending
    initContainerStatuses:
      - name: restartable-init-1
        ready: false
        restartCount: 3
        state:
          running: {}
        started: true
        lastTerminationState:
          terminated:
            finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
      - name: restartable-init-2
        ready: false
        state:
          running: {}
        started: false
    containerStatuses:
      - ready: false
        restartCount: 0
        state:
          waiting: {}
    conditions:
      - type: ContainersReady
        status: "False"
      - type: Initialized
        status: "False"
`)

		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Init:1/2"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/3"},
			{Name: "Restart Count", Value: "3"},
			{Name: common.PodRequestsCPU, Value: "0"},
			{Name: common.PodRequestsMEM, Value: "0"},
		}, info.Info)
	})

	// Test pod has 2 restartable init containers started and 1 container running
	t.Run("TestGetPodInfoWithStartedInitContainers", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test2
  spec:
    initContainers:
      - name: restartable-init-1
        restartPolicy: Always
      - name: restartable-init-2
        restartPolicy: Always
    containers:
      - name: container
    nodeName: minikube
  status:
    phase: Running
    initContainerStatuses:
      - name: restartable-init-1
        ready: false
        restartCount: 3
        state:
          running: {}
        started: true
        lastTerminationState:
          terminated:
            finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
      - name: restartable-init-2
        ready: false
        state:
          running: {}
        started: true
    containerStatuses:
      - ready: true
        restartCount: 4
        state:
          running: {}
        lastTerminationState:
          terminated:
            finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
    conditions:
      - type: ContainersReady
        status: "False"
      - type: Initialized
        status: "True"
`)

		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Running"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "1/3"},
			{Name: "Restart Count", Value: "7"},
			{Name: common.PodRequestsCPU, Value: "0"},
			{Name: common.PodRequestsMEM, Value: "0"},
		}, info.Info)
	})

	// Test pod has 1 init container restarting and 1 container not running
	t.Run("TestGetPodInfoWithNormalInitContainer", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test7
  spec:
    initContainers:
      - name: init-container
    containers:
      - name: main-container
    nodeName: minikube
  status:
    phase: podPhase
    initContainerStatuses:
      - ready: false
        restartCount: 3
        state:
          running: {}
        lastTerminationState:
          terminated:
            finishedAt: "2023-10-01T00:00:00Z" # Replace with the actual time
    containerStatuses:
      - ready: false
        restartCount: 0
        state:
          waiting: {}
`)

		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Init:0/1"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/1"},
			{Name: "Restart Count", Value: "3"},
			{Name: common.PodRequestsCPU, Value: "0"},
			{Name: common.PodRequestsMEM, Value: "0"},
		}, info.Info)
	})

	// Test pod condition succeed
	t.Run("TestPodConditionSucceeded", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test8
  spec:
    nodeName: minikube
    containers:
      - name: container
  status:
    phase: Succeeded
    containerStatuses:
      - ready: false
        restartCount: 0
        state:
          terminated:
            reason: Completed
            exitCode: 0
`)
		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Completed"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/1"},
		}, info.Info)
	})

	// Test pod condition succeed which had some allocated resources
	t.Run("TestPodConditionSucceededWithResources", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test8
  spec:
    nodeName: minikube
    containers:
      - name: container
        resources:
          requests:
            cpu: "50m"
            memory: "64Mi"
          limits:
            cpu: "250m"
            memory: "256Mi"
  status:
    phase: Succeeded
    containerStatuses:
      - ready: false
        restartCount: 0
        state:
          terminated:
            reason: Completed
            exitCode: 0
`)
		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Completed"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/1"},
		}, info.Info)
	})

	// Test pod condition failed
	t.Run("TestPodConditionFailed", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test9
  spec:
    nodeName: minikube
    containers:
      - name: container
  status:
    phase: Failed
    containerStatuses:
      - ready: false
        restartCount: 0
        state:
          terminated:
            reason: Error
            exitCode: 1
`)
		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Error"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/1"},
		}, info.Info)
	})

	// Test pod condition failed with allocated resources

	t.Run("TestPodConditionFailedWithResources", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test9
  spec:
    nodeName: minikube
    containers:
      - name: container
        resources:
          requests:
            cpu: "50m"
            memory: "64Mi"
          limits:
            cpu: "250m"
            memory: "256Mi"
  status:
    phase: Failed
    containerStatuses:
      - ready: false
        restartCount: 0
        state:
          terminated:
            reason: Error
            exitCode: 1
`)
		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Error"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/1"},
		}, info.Info)
	})

	// Test pod condition succeed with deletion
	t.Run("TestPodConditionSucceededWithDeletion", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test10
    deletionTimestamp: "2023-10-01T00:00:00Z"
  spec:
    nodeName: minikube
    containers:
      - name: container
  status:
    phase: Succeeded
    containerStatuses:
      - ready: false
        restartCount: 0
        state:
          terminated:
            reason: Completed
            exitCode: 0
`)
		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Completed"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/1"},
		}, info.Info)
	})

	// Test pod condition running with deletion
	t.Run("TestPodConditionRunningWithDeletion", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test11
    deletionTimestamp: "2023-10-01T00:00:00Z"
  spec:
    nodeName: minikube
    containers:
      - name: container
  status:
    phase: Running
    containerStatuses:
      - ready: false
        restartCount: 0
        state:
          running: {}
`)
		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Terminating"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/1"},
			{Name: common.PodRequestsCPU, Value: "0"},
			{Name: common.PodRequestsMEM, Value: "0"},
		}, info.Info)
	})

	// Test pod condition pending with deletion
	t.Run("TestPodConditionPendingWithDeletion", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test12
    deletionTimestamp: "2023-10-01T00:00:00Z"
  spec:
    nodeName: minikube
    containers:
      - name: container
  status:
    phase: Pending
`)
		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "Terminating"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/1"},
			{Name: common.PodRequestsCPU, Value: "0"},
			{Name: common.PodRequestsMEM, Value: "0"},
		}, info.Info)
	})

	// Test PodScheduled condition with reason SchedulingGated
	t.Run("TestPodScheduledWithSchedulingGated", func(t *testing.T) {
		t.Parallel()

		pod := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: test13
  spec:
    nodeName: minikube
    containers:
      - name: container1
      - name: container2
  status:
    phase: podPhase
    conditions:
      - type: PodScheduled
        status: "False"
        reason: SchedulingGated
`)
		info := &ResourceInfo{}
		populateNodeInfo(pod, info, []string{})
		assert.Equal(t, []v1alpha1.InfoItem{
			{Name: "Status Reason", Value: "SchedulingGated"},
			{Name: "Node", Value: "minikube"},
			{Name: "Containers", Value: "0/2"},
			{Name: common.PodRequestsCPU, Value: "0"},
			{Name: common.PodRequestsMEM, Value: "0"},
		}, info.Info)
	})
}

func TestGetNodeInfo(t *testing.T) {
	node := strToUnstructured(`
apiVersion: v1
kind: Node
metadata:
  name: minikube
  labels:
    foo: bar
spec: {}
status:
  capacity:
    cpu: "6"
    memory: 6091320Ki
  nodeInfo:
    architecture: amd64
    operatingSystem: linux
    osImage: Ubuntu 20.04 LTS
`)

	info := &ResourceInfo{}
	populateNodeInfo(node, info, []string{})
	assert.Equal(t, &NodeInfo{
		Name:       "minikube",
		Capacity:   corev1.ResourceList{corev1.ResourceMemory: resource.MustParse("6091320Ki"), corev1.ResourceCPU: resource.MustParse("6")},
		SystemInfo: corev1.NodeSystemInfo{Architecture: "amd64", OperatingSystem: "linux", OSImage: "Ubuntu 20.04 LTS"},
		Labels:     map[string]string{"foo": "bar"},
	}, info.NodeInfo)
}

func TestGetServiceInfo(t *testing.T) {
	info := &ResourceInfo{}
	populateNodeInfo(testService, info, []string{})
	assert.Empty(t, info.Info)
	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
		TargetLabels: map[string]string{"app": "guestbook"},
		Ingress:      []corev1.LoadBalancerIngress{{Hostname: "localhost"}},
	}, info.NetworkingInfo)
}

func TestGetLinkAnnotatedServiceInfo(t *testing.T) {
	info := &ResourceInfo{}
	populateNodeInfo(testLinkAnnotatedService, info, []string{})
	assert.Empty(t, info.Info)
	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
		TargetLabels: map[string]string{"app": "guestbook"},
		Ingress:      []corev1.LoadBalancerIngress{{Hostname: "localhost"}},
		ExternalURLs: []string{"http://my-grafana.example.com/pre-generated-link"},
	}, info.NetworkingInfo)
}

func TestMaliciousLinkAnnotatedServiceInfoFiltered(t *testing.T) {
	info := &ResourceInfo{}
	populateNodeInfo(testMaliciousLinkAnnotatedService, info, []string{})
	require.NotNil(t, info.NetworkingInfo)
	// Only the http URL should make it through; javascript:, data:, vbscript:,
	// "title|javascript:..." and scheme-less values must be dropped.
	assert.Equal(t, []string{"http://my-grafana.example.com/pre-generated-link"}, info.NetworkingInfo.ExternalURLs)
}

func TestGetIstioVirtualServiceInfo(t *testing.T) {
	info := &ResourceInfo{}
	populateNodeInfo(testIstioVirtualService, info, []string{})
	assert.Empty(t, info.Info)
	require.NotNil(t, info.NetworkingInfo)
	require.NotNil(t, info.NetworkingInfo.TargetRefs)
	assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{
		Kind:      kube.ServiceKind,
		Name:      "service_full",
		Namespace: "demo",
	})
	assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{
		Kind:      kube.ServiceKind,
		Name:      "service_namespace",
		Namespace: "namespace",
	})
	assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{
		Kind:      kube.ServiceKind,
		Name:      "service",
		Namespace: "demo",
	})
}

func TestGetIstioServiceEntryInfo(t *testing.T) {
	info := &ResourceInfo{}
	populateNodeInfo(testIstioServiceEntry, info, []string{})
	assert.Empty(t, info.Info)
	require.NotNil(t, info.NetworkingInfo)
	require.NotNil(t, info.NetworkingInfo.TargetRefs)
	assert.Contains(t, info.NetworkingInfo.TargetRefs, v1alpha1.ResourceRef{
		Kind: kube.PodKind,
	})

	assert.Equal(t, map[string]string{
		"app.kubernetes.io/name": "echo-2",
	}, info.NetworkingInfo.TargetLabels)
}

func TestGetIngressInfo(t *testing.T) {
	tests := []struct {
		Ingress *unstructured.Unstructured
	}{
		{testIngress},
		{testIngressNetworkingV1},
	}
	for _, tc := range tests {
		info := &ResourceInfo{}
		populateNodeInfo(tc.Ingress, info, []string{})
		assert.Empty(t, info.Info)
		sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
			return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name
		})
		assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
			Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}},
			TargetRefs: []v1alpha1.ResourceRef{{
				Namespace: "default",
				Group:     "",
				Kind:      kube.ServiceKind,
				Name:      "helm-guestbook",
			}, {
				Namespace: "default",
				Group:     "",
				Kind:      kube.ServiceKind,
				Name:      "not-found-service",
			}},
			ExternalURLs: []string{"https://helm-guestbook.example.com/"},
		}, info.NetworkingInfo)
	}
}

func TestGetLinkAnnotatedIngressInfo(t *testing.T) {
	info := &ResourceInfo{}
	populateNodeInfo(testLinkAnnotatedIngress, info, []string{})
	assert.Empty(t, info.Info)
	sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
		return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name
	})
	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
		Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}},
		TargetRefs: []v1alpha1.ResourceRef{{
			Namespace: "default",
			Group:     "",
			Kind:      kube.ServiceKind,
			Name:      "helm-guestbook",
		}, {
			Namespace: "default",
			Group:     "",
			Kind:      kube.ServiceKind,
			Name:      "not-found-service",
		}},
		ExternalURLs: []string{"http://my-grafana.example.com/ingress-link", "https://helm-guestbook.example.com/"},
	}, info.NetworkingInfo)
}

func TestGetIgnoreDefaultLinksIngressInfo(t *testing.T) {
	info := &ResourceInfo{}
	populateNodeInfo(testIgnoreDefaultLinksIngress, info, []string{})
	assert.Empty(t, info.Info)
	sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
		return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name
	})
	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
		Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}},
		TargetRefs: []v1alpha1.ResourceRef{{
			Namespace: "default",
			Group:     "",
			Kind:      kube.ServiceKind,
			Name:      "helm-guestbook",
		}, {
			Namespace: "default",
			Group:     "",
			Kind:      kube.ServiceKind,
			Name:      "not-found-service",
		}},
		ExternalURLs: []string{"http://my-grafana.example.com/ingress-link"},
	}, info.NetworkingInfo)
}

func TestGetIngressInfoWildCardPath(t *testing.T) {
	info := &ResourceInfo{}
	populateNodeInfo(testIngressWildCardPath, info, []string{})
	assert.Empty(t, info.Info)
	sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
		return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name
	})
	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
		Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}},
		TargetRefs: []v1alpha1.ResourceRef{{
			Namespace: "default",
			Group:     "",
			Kind:      kube.ServiceKind,
			Name:      "helm-guestbook",
		}, {
			Namespace: "default",
			Group:     "",
			Kind:      kube.ServiceKind,
			Name:      "not-found-service",
		}},
		ExternalURLs: []string{"https://helm-guestbook.example.com/"},
	}, info.NetworkingInfo)
}

func TestGetIngressInfoWithoutTls(t *testing.T) {
	info := &ResourceInfo{}
	populateNodeInfo(testIngressWithoutTLS, info, []string{})
	assert.Empty(t, info.Info)
	sort.Slice(info.NetworkingInfo.TargetRefs, func(i, j int) bool {
		return info.NetworkingInfo.TargetRefs[i].Name < info.NetworkingInfo.TargetRefs[j].Name
	})
	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
		Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}},
		TargetRefs: []v1alpha1.ResourceRef{{
			Namespace: "default",
			Group:     "",
			Kind:      kube.ServiceKind,
			Name:      "helm-guestbook",
		}, {
			Namespace: "default",
			Group:     "",
			Kind:      kube.ServiceKind,
			Name:      "not-found-service",
		}},
		ExternalURLs: []string{"http://helm-guestbook.example.com/"},
	}, info.NetworkingInfo)
}

func TestGetIngressInfoWithHost(t *testing.T) {
	ingress := strToUnstructured(`
  apiVersion: extensions/v1beta1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
  spec:
    rules:
    - http:
        paths:
        - backend:
            serviceName: helm-guestbook
            servicePort: 443
          path: /
    tls:
    - secretName: my-tls
  status:
    loadBalancer:
      ingress:
      - ip: 107.178.210.11`)

	info := &ResourceInfo{}
	populateNodeInfo(ingress, info, []string{})

	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
		Ingress: []corev1.LoadBalancerIngress{{IP: "107.178.210.11"}},
		TargetRefs: []v1alpha1.ResourceRef{{
			Namespace: "default",
			Group:     "",
			Kind:      kube.ServiceKind,
			Name:      "helm-guestbook",
		}},
		ExternalURLs: []string{"https://107.178.210.11/"},
	}, info.NetworkingInfo)
}

func TestGetIngressInfoNoHost(t *testing.T) {
	ingress := strToUnstructured(`
  apiVersion: extensions/v1beta1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
  spec:
    rules:
    - http:
        paths:
        - backend:
            serviceName: helm-guestbook
            servicePort: 443
          path: /
    tls:
    - secretName: my-tls
      `)

	info := &ResourceInfo{}
	populateNodeInfo(ingress, info, []string{})

	assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{
		TargetRefs: []v1alpha1.ResourceRef{{
			Namespace: "default",
			Group:     "",
			Kind:      kube.ServiceKind,
			Name:      "helm-guestbook",
		}},
	}, info.NetworkingInfo)
	assert.Empty(t, info.NetworkingInfo.ExternalURLs)
}

func TestExternalUrlWithSubPath(t *testing.T) {
	ingress := strToUnstructured(`
  apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
  spec:
    rules:
    - http:
        paths:
        - backend:
            serviceName: helm-guestbook
            servicePort: 443
          path: /my/sub/path/
    tls:
    - secretName: my-tls
  status:
    loadBalancer:
      ingress:
      - ip: 107.178.210.11`)

	info := &ResourceInfo{}
	populateNodeInfo(ingress, info, []string{})

	expectedExternalUrls := []string{"https://107.178.210.11/my/sub/path/"}
	assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
}

func TestExternalUrlWithMultipleSubPaths(t *testing.T) {
	ingress := strToUnstructured(`
  apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
  spec:
    rules:
    - host: helm-guestbook.example.com
      http:
        paths:
        - backend:
            serviceName: helm-guestbook
            servicePort: 443
          path: /my/sub/path/
        - backend:
            serviceName: helm-guestbook-2
            servicePort: 443
          path: /my/sub/path/2
        - backend:
            serviceName: helm-guestbook-3
            servicePort: 443
    tls:
    - secretName: my-tls
  status:
    loadBalancer:
      ingress:
      - ip: 107.178.210.11`)

	info := &ResourceInfo{}
	populateNodeInfo(ingress, info, []string{})

	expectedExternalUrls := []string{"https://helm-guestbook.example.com/my/sub/path/", "https://helm-guestbook.example.com/my/sub/path/2", "https://helm-guestbook.example.com"}
	actualURLs := info.NetworkingInfo.ExternalURLs
	sort.Strings(expectedExternalUrls)
	sort.Strings(actualURLs)
	assert.Equal(t, expectedExternalUrls, actualURLs)
}

func TestExternalUrlWithNoSubPath(t *testing.T) {
	ingress := strToUnstructured(`
  apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
  spec:
    rules:
    - http:
        paths:
        - backend:
            serviceName: helm-guestbook
            servicePort: 443
    tls:
    - secretName: my-tls
  status:
    loadBalancer:
      ingress:
      - ip: 107.178.210.11`)

	info := &ResourceInfo{}
	populateNodeInfo(ingress, info, []string{})

	expectedExternalUrls := []string{"https://107.178.210.11"}
	assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
}

func TestExternalUrlWithNetworkingApi(t *testing.T) {
	ingress := strToUnstructured(`
  apiVersion: networking.k8s.io/v1beta1
  kind: Ingress
  metadata:
    name: helm-guestbook
    namespace: default
  spec:
    rules:
    - http:
        paths:
        - backend:
            serviceName: helm-guestbook
            servicePort: 443
    tls:
    - secretName: my-tls
  status:
    loadBalancer:
      ingress:
      - ip: 107.178.210.11`)

	info := &ResourceInfo{}
	populateNodeInfo(ingress, info, []string{})

	expectedExternalUrls := []string{"https://107.178.210.11"}
	assert.Equal(t, expectedExternalUrls, info.NetworkingInfo.ExternalURLs)
}

func TestCustomLabel(t *testing.T) {
	configmap := strToUnstructured(`
  apiVersion: v1
  kind: ConfigMap
  metadata:
    name: cm`)

	info := &ResourceInfo{}
	populateNodeInfo(configmap, info, []string{"my-label"})

	assert.Empty(t, info.Info)

	configmap = strToUnstructured(`
  apiVersion: v1
  kind: ConfigMap
  metadata:
    name: cm
    labels:
      my-label: value`)

	info = &ResourceInfo{}
	populateNodeInfo(configmap, info, []string{"my-label", "other-label"})

	assert.Len(t, info.Info, 1)
	assert.Equal(t, "my-label", info.Info[0].Name)
	assert.Equal(t, "value", info.Info[0].Value)

	configmap = strToUnstructured(`
  apiVersion: v1
  kind: ConfigMap
  metadata:
    name: cm
    labels:
      my-label: value
      other-label: value2`)

	info = &ResourceInfo{}
	populateNodeInfo(configmap, info, []string{"my-label", "other-label"})

	assert.Len(t, info.Info, 2)
	assert.Equal(t, "my-label", info.Info[0].Name)
	assert.Equal(t, "value", info.Info[0].Value)
	assert.Equal(t, "other-label", info.Info[1].Name)
	assert.Equal(t, "value2", info.Info[1].Value)
}

func TestManifestHash(t *testing.T) {
	manifest := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: helm-guestbook-pod
    namespace: default
    ownerReferences:
    - apiVersion: extensions/v1beta1
      kind: ReplicaSet
      name: helm-guestbook-rs
    resourceVersion: "123"
    labels:
      app: guestbook
  spec:
    nodeName: minikube
    containers:
    - image: bar
      resources:
        requests:
          memory: 128Mi
`)

	ignores := []v1alpha1.ResourceIgnoreDifferences{
		{
			Group:        "*",
			Kind:         "*",
			JSONPointers: []string{"/metadata/resourceVersion"},
		},
	}

	data, _ := strToUnstructured(`
  apiVersion: v1
  kind: Pod
  metadata:
    name: helm-guestbook-pod
    namespace: default
    ownerReferences:
    - apiVersion: extensions/v1beta1
      kind: ReplicaSet
      name: helm-guestbook-rs
    labels:
      app: guestbook
  spec:
    nodeName: minikube
    containers:
    - image: bar
      resources:
        requests:
          memory: 128Mi
`).MarshalJSON()

	expected := hash(data)

	hash, err := generateManifestHash(manifest, ignores, nil, normalizers.IgnoreNormalizerOpts{})
	assert.Equal(t, expected, hash)
	assert.NoError(t, err)
}
