/*
Copyright 2021 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package resourcemonitor

import (
	"fmt"
	"reflect"
	"sort"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/mock"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	k8sclient "k8s.io/client-go/kubernetes"
	v1 "k8s.io/kubelet/pkg/apis/podresources/v1"

	"sigs.k8s.io/node-feature-discovery/pkg/apihelper"
	"sigs.k8s.io/node-feature-discovery/pkg/podres"
)

func TestPodScanner(t *testing.T) {

	var resScan ResourcesScanner
	var err error

	Convey("When I scan for pod resources using fake client and no namespace", t, func() {
		mockPodResClient := new(podres.MockPodResourcesListerClient)
		mockAPIHelper := new(apihelper.MockAPIHelpers)
		mockClient := &k8sclient.Clientset{}
		resScan, err = NewPodResourcesScanner("*", mockPodResClient, mockAPIHelper)

		Convey("Creating a Resources Scanner using a mock client", func() {
			So(err, ShouldBeNil)
		})

		Convey("When I get error", func() {
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(nil, fmt.Errorf("fake error"))
			res, err := resScan.Scan()

			Convey("Error is present", func() {
				So(err, ShouldNotBeNil)
			})
			Convey("Return PodResources should be nil", func() {
				So(res, ShouldBeNil)
			})
		})

		Convey("When I successfully get empty response", func() {
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(&v1.ListPodResourcesResponse{}, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should be zero", func() {
				So(len(res), ShouldEqual, 0)
			})
		})

		Convey("When I successfully get valid response", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []*v1.ContainerResources{
							{
								Name: "test-cnt-0",
								Devices: []*v1.ContainerDevices{
									{
										ResourceName: "fake.io/resource",
										DeviceIds:    []string{"devA"},
										Topology: &v1.TopologyInfo{
											Nodes: []*v1.NUMANode{
												{ID: 0},
											},
										},
									},
								},
								CpuIds: []int64{0, 1},
								Memory: []*v1.ContainerMemory{
									{
										MemoryType: "hugepages-2Mi",
										Size_:      512,
										Topology: &v1.TopologyInfo{
											Nodes: []*v1.NUMANode{
												{ID: 1},
											},
										},
									},
									{
										MemoryType: "memory",
										Size_:      512,
										Topology: &v1.TopologyInfo{
											Nodes: []*v1.NUMANode{
												{ID: 0},
											},
										},
									},
								},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "default",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name: "test-cnt-0",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(512, resource.DecimalSI),
									"hugepages-2Mi":                         *resource.NewQuantity(512, resource.DecimalSI),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(512, resource.DecimalSI),
									"hugepages-2Mi":                         *resource.NewQuantity(512, resource.DecimalSI),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should have values", func() {
				So(len(res), ShouldBeGreaterThan, 0)

				expected := []PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []ContainerResources{
							{
								Name: "test-cnt-0",
								Resources: []ResourceInfo{
									{
										Name: "cpu",
										Data: []string{"0", "1"},
									},
									{
										Name:        "fake.io/resource",
										Data:        []string{"devA"},
										NumaNodeIds: []int{0},
									},
									{
										Name:        "hugepages-2Mi",
										Data:        []string{"512"},
										NumaNodeIds: []int{1},
									},
									{
										Name:        "memory",
										Data:        []string{"512"},
										NumaNodeIds: []int{0},
									},
								},
							},
						},
					},
				}
				for _, podresource := range res {
					for _, container := range podresource.Containers {
						sort.Slice(res, func(i, j int) bool {
							return container.Resources[i].Name < container.Resources[j].Name
						})
					}
				}
				So(reflect.DeepEqual(res, expected), ShouldBeTrue)
			})
		})

		Convey("When I successfully get valid response without topology", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []*v1.ContainerResources{
							{
								Name: "test-cnt-0",
								Devices: []*v1.ContainerDevices{
									{
										ResourceName: "fake.io/resource",
										DeviceIds:    []string{"devA"},
									},
								},
								CpuIds: []int64{0, 1},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "default",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:            "test-cnt-0",
							Image:           "nginx",
							ImagePullPolicy: "Always",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should have values", func() {
				So(len(res), ShouldBeGreaterThan, 0)

				expected := []PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []ContainerResources{
							{
								Name: "test-cnt-0",
								Resources: []ResourceInfo{
									{
										Name: "cpu",
										Data: []string{"0", "1"},
									},
									{
										Name: "fake.io/resource",
										Data: []string{"devA"},
									},
								},
							},
						},
					},
				}

				So(reflect.DeepEqual(res, expected), ShouldBeTrue)
			})
		})

		Convey("When I successfully get valid response without devices", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []*v1.ContainerResources{
							{
								Name:   "test-cnt-0",
								CpuIds: []int64{0, 1},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "default",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name: "test-cnt-0",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:    *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceCPU:    *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should have values", func() {
				So(len(res), ShouldBeGreaterThan, 0)

				expected := []PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []ContainerResources{
							{
								Name: "test-cnt-0",
								Resources: []ResourceInfo{
									{
										Name: "cpu",
										Data: []string{"0", "1"},
									},
								},
							},
						},
					},
				}

				So(reflect.DeepEqual(res, expected), ShouldBeTrue)
			})
		})

		Convey("When I successfully get valid response without cpus", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []*v1.ContainerResources{
							{
								Name: "test-cnt-0",
								Devices: []*v1.ContainerDevices{
									{
										ResourceName: "fake.io/resource",
										DeviceIds:    []string{"devA"},
									},
								},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "default",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:            "test-cnt-0",
							Image:           "nginx",
							ImagePullPolicy: "Always",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should have values", func() {
				So(len(res), ShouldBeGreaterThan, 0)

				expected := []PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []ContainerResources{
							{
								Name: "test-cnt-0",
								Resources: []ResourceInfo{
									{
										Name: "fake.io/resource",
										Data: []string{"devA"},
									},
								},
							},
						},
					},
				}

				So(reflect.DeepEqual(res, expected), ShouldBeTrue)
			})
		})

		Convey("When I successfully get valid response for (non-guaranteed) pods with devices without cpus", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []*v1.ContainerResources{
							{
								Name: "test-cnt-0",
								Devices: []*v1.ContainerDevices{
									{
										ResourceName: "fake.io/resource",
										DeviceIds:    []string{"devA"},
									},
								},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "default",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name: "test-cnt-0",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should have values", func() {
				So(len(res), ShouldBeGreaterThan, 0)
			})

			expected := []PodResources{
				{
					Name:      "test-pod-0",
					Namespace: "default",
					Containers: []ContainerResources{
						{
							Name: "test-cnt-0",
							Resources: []ResourceInfo{
								{
									Name: "fake.io/resource",
									Data: []string{"devA"},
								},
							},
						},
					},
				},
			}

			So(reflect.DeepEqual(res, expected), ShouldBeTrue)
		})

		Convey("When I successfully get valid response for (non-guaranteed) pods with devices with cpus", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []*v1.ContainerResources{
							{
								Name:   "test-cnt-0",
								CpuIds: []int64{0, 1},
								Devices: []*v1.ContainerDevices{
									{
										ResourceName: "fake.io/resource",
										DeviceIds:    []string{"devA"},
									},
								},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "default",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name: "test-cnt-0",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{

									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
									corev1.ResourceCPU:                      resource.MustParse("1500m"),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
									corev1.ResourceCPU:                      resource.MustParse("1500m"),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should have values", func() {
				So(len(res), ShouldBeGreaterThan, 0)
			})

			expected := []PodResources{
				{
					Name:      "test-pod-0",
					Namespace: "default",
					Containers: []ContainerResources{
						{
							Name: "test-cnt-0",
							Resources: []ResourceInfo{
								{
									Name: "fake.io/resource",
									Data: []string{"devA"},
								},
							},
						},
					},
				},
			}
			So(reflect.DeepEqual(res, expected), ShouldBeTrue)
		})

	})

	Convey("When I scan for pod resources using fake client and given namespace", t, func() {
		mockPodResClient := new(podres.MockPodResourcesListerClient)
		mockAPIHelper := new(apihelper.MockAPIHelpers)
		mockClient := &k8sclient.Clientset{}
		resScan, err = NewPodResourcesScanner("pod-res-test", mockPodResClient, mockAPIHelper)

		Convey("Creating a Resources Scanner using a mock client", func() {
			So(err, ShouldBeNil)
		})

		Convey("When I get error", func() {
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(nil, fmt.Errorf("fake error"))
			res, err := resScan.Scan()

			Convey("Error is present", func() {
				So(err, ShouldNotBeNil)
			})
			Convey("Return PodResources should be nil", func() {
				So(res, ShouldBeNil)
			})
		})

		Convey("When I successfully get empty response", func() {
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(&v1.ListPodResourcesResponse{}, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should be zero", func() {
				So(len(res), ShouldEqual, 0)
			})
		})

		Convey("When I successfully get valid response", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []*v1.ContainerResources{
							{
								Name: "test-cnt-0",
								Devices: []*v1.ContainerDevices{
									{
										ResourceName: "fake.io/resource",
										DeviceIds:    []string{"devA"},
										Topology: &v1.TopologyInfo{
											Nodes: []*v1.NUMANode{
												{ID: 0},
											},
										},
									},
								},
								CpuIds: []int64{0, 1},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "default",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name: "test-cnt-0",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should be zero", func() {
				So(len(res), ShouldEqual, 0)
			})
		})

		Convey("When I successfully get valid response when pod is in the monitoring namespace", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "pod-res-test",
						Containers: []*v1.ContainerResources{
							{
								Name: "test-cnt-0",
								Devices: []*v1.ContainerDevices{
									{
										ResourceName: "fake.io/resource",
										DeviceIds:    []string{"devA"},
									},
								},
								CpuIds: []int64{0, 1},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "pod-res-test",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:            "test-cnt-0",
							Image:           "nginx",
							ImagePullPolicy: "Always",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "pod-res-test", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})

			Convey("Return PodResources should have values", func() {
				So(len(res), ShouldBeGreaterThan, 0)

				expected := []PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "pod-res-test",
						Containers: []ContainerResources{
							{
								Name: "test-cnt-0",
								Resources: []ResourceInfo{
									{
										Name: "cpu",
										Data: []string{"0", "1"},
									},
									{
										Name: "fake.io/resource",
										Data: []string{"devA"},
									},
								},
							},
						},
					},
				}

				So(reflect.DeepEqual(res, expected), ShouldBeTrue)
			})
		})

		Convey("When I successfully get valid empty response for a pod not in the monitoring namespace without devices", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []*v1.ContainerResources{
							{
								Name:   "test-cnt-0",
								CpuIds: []int64{0, 1},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "default",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name: "test-cnt-0",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:    *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceCPU:    *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceMemory: *resource.NewQuantity(100, resource.DecimalSI),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should have values", func() {
				Convey("Return PodResources should be zero", func() {
					So(len(res), ShouldEqual, 0)
				})
			})
		})

		Convey("When I successfully get an empty valid response for a pod without cpus when pod is not in the monitoring namespace", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "default",
						Containers: []*v1.ContainerResources{
							{
								Name: "test-cnt-0",
								Devices: []*v1.ContainerDevices{
									{
										ResourceName: "fake.io/resource",
										DeviceIds:    []string{"devA"},
									},
								},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "default",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name:            "test-cnt-0",
							Image:           "nginx",
							ImagePullPolicy: "Always",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceCPU:                      *resource.NewQuantity(2, resource.DecimalSI),
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "default", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should have values", func() {
				So(len(res), ShouldEqual, 0)
			})
		})

		Convey("When I successfully get valid response for (non-guaranteed) pods with devices without cpus", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "pod-res-test",
						Containers: []*v1.ContainerResources{
							{
								Name: "test-cnt-0",
								Devices: []*v1.ContainerDevices{
									{
										ResourceName: "fake.io/resource",
										DeviceIds:    []string{"devA"},
									},
								},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "pod-res-test",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name: "test-cnt-0",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "pod-res-test", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should have values", func() {
				So(len(res), ShouldBeGreaterThan, 0)
			})

			expected := []PodResources{
				{
					Name:      "test-pod-0",
					Namespace: "pod-res-test",
					Containers: []ContainerResources{
						{
							Name: "test-cnt-0",
							Resources: []ResourceInfo{
								{
									Name: "fake.io/resource",
									Data: []string{"devA"},
								},
							},
						},
					},
				},
			}

			So(reflect.DeepEqual(res, expected), ShouldBeTrue)
		})

		Convey("When I successfully get valid response for (non-guaranteed) pods with devices with cpus", func() {
			resp := &v1.ListPodResourcesResponse{
				PodResources: []*v1.PodResources{
					{
						Name:      "test-pod-0",
						Namespace: "pod-res-test",
						Containers: []*v1.ContainerResources{
							{
								Name:   "test-cnt-0",
								CpuIds: []int64{0, 1},
								Devices: []*v1.ContainerDevices{
									{
										ResourceName: "fake.io/resource",
										DeviceIds:    []string{"devA"},
									},
								},
							},
						},
					},
				},
			}
			mockPodResClient.On("List", mock.AnythingOfType("*context.timerCtx"), mock.AnythingOfType("*v1.ListPodResourcesRequest")).Return(resp, nil)
			pod := &corev1.Pod{
				TypeMeta: metav1.TypeMeta{
					Kind:       "Pod",
					APIVersion: "v1",
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      "test-pod-0",
					Namespace: "pod-res-test",
				},
				Spec: corev1.PodSpec{
					Containers: []corev1.Container{
						{
							Name: "test-cnt-0",
							Resources: corev1.ResourceRequirements{
								Requests: corev1.ResourceList{

									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
									corev1.ResourceCPU:                      resource.MustParse("1500m"),
								},
								Limits: corev1.ResourceList{
									corev1.ResourceName("fake.io/resource"): *resource.NewQuantity(1, resource.DecimalSI),
									corev1.ResourceMemory:                   *resource.NewQuantity(100, resource.DecimalSI),
									corev1.ResourceCPU:                      resource.MustParse("1500m"),
								},
							},
						},
					},
				},
			}
			mockAPIHelper.On("GetClient").Return(mockClient, nil)
			mockAPIHelper.On("GetPod", mock.AnythingOfType("*kubernetes.Clientset"), "pod-res-test", "test-pod-0").Return(pod, nil)
			res, err := resScan.Scan()

			Convey("Error is nil", func() {
				So(err, ShouldBeNil)
			})
			Convey("Return PodResources should have values", func() {
				So(len(res), ShouldBeGreaterThan, 0)
			})

			expected := []PodResources{
				{
					Name:      "test-pod-0",
					Namespace: "pod-res-test",
					Containers: []ContainerResources{
						{
							Name: "test-cnt-0",
							Resources: []ResourceInfo{
								{
									Name: "fake.io/resource",
									Data: []string{"devA"},
								},
							},
						},
					},
				},
			}
			So(reflect.DeepEqual(res, expected), ShouldBeTrue)
		})

	})
}