mirror of
https://github.com/kubescape/kubescape.git
synced 2026-02-14 18:09:55 +00:00
test(hostsensorutils): added unit tests to the hostsensorutils package
This PR introduces a (limited) mock for the kubernetes client API. Signed-off-by: Frederic BIDON <fredbi@yahoo.com>
This commit is contained in:
23
core/pkg/hostsensorutils/hostsensor_mock_test.go
Normal file
23
core/pkg/hostsensorutils/hostsensor_mock_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package hostsensorutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHostSensorHandlerMock(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
h := &HostSensorHandlerMock{}
|
||||
|
||||
require.NoError(t, h.Init(ctx))
|
||||
|
||||
envelope, status, err := h.CollectResources(ctx)
|
||||
require.Empty(t, envelope)
|
||||
require.Nil(t, status)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Empty(t, h.GetNamespace())
|
||||
require.NoError(t, h.TearDown())
|
||||
}
|
||||
246
core/pkg/hostsensorutils/hostsensordeploy_test.go
Normal file
246
core/pkg/hostsensorutils/hostsensordeploy_test.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package hostsensorutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/kubescape/opa-utils/objectsenvelopes/hostsensor"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestHostSensorHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("with default manifest", func(t *testing.T) {
|
||||
t.Run("should build host sensor", func(t *testing.T) {
|
||||
k8s := NewKubernetesApiMock(WithNode(mockNode1()), WithPod(mockPod1()), WithPod(mockPod2()), WithResponses(mockResponses()))
|
||||
h, err := NewHostSensorHandler(k8s, "")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, h)
|
||||
|
||||
t.Run("should initialize host sensor", func(t *testing.T) {
|
||||
require.NoError(t, h.Init(ctx))
|
||||
|
||||
w, err := k8s.KubernetesClient.CoreV1().Pods(h.DaemonSet.Namespace).Watch(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
w.Stop()
|
||||
|
||||
require.Len(t, h.HostSensorPodNames, 2)
|
||||
})
|
||||
|
||||
t.Run("should return namespace", func(t *testing.T) {
|
||||
require.Equal(t, "kubescape-host-scanner", h.GetNamespace())
|
||||
})
|
||||
|
||||
t.Run("should collect resources from pods - happy path", func(t *testing.T) {
|
||||
envelope, status, err := h.CollectResources(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, envelope, 11*2) // has cloud provider, no control plane requested
|
||||
require.Len(t, status, 0)
|
||||
|
||||
foundControl, foundProvider := false, false
|
||||
for _, sensed := range envelope {
|
||||
if sensed.Kind == ControlPlaneInfo {
|
||||
foundControl = true
|
||||
}
|
||||
if sensed.Kind == CloudProviderInfo {
|
||||
foundProvider = hasCloudProviderInfo([]hostsensor.HostSensorDataEnvelope{sensed})
|
||||
}
|
||||
}
|
||||
|
||||
require.False(t, foundControl)
|
||||
require.True(t, foundProvider)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should build host sensor without cloud provider", func(t *testing.T) {
|
||||
k8s := NewKubernetesApiMock(WithNode(mockNode1()), WithPod(mockPod1()), WithPod(mockPod2()), WithResponses(mockResponsesNoCloudProvider()))
|
||||
h, err := NewHostSensorHandler(k8s, "")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, h)
|
||||
|
||||
t.Run("should initialize host sensor", func(t *testing.T) {
|
||||
require.NoError(t, h.Init(ctx))
|
||||
|
||||
w, err := k8s.KubernetesClient.CoreV1().Pods(h.DaemonSet.Namespace).Watch(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
w.Stop()
|
||||
|
||||
require.Len(t, h.HostSensorPodNames, 2)
|
||||
})
|
||||
|
||||
t.Run("should get version", func(t *testing.T) {
|
||||
version, err := h.GetVersion()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "v1.0.45", version)
|
||||
})
|
||||
|
||||
t.Run("ForwardToPod is a stub, not implemented", func(t *testing.T) {
|
||||
// NOTE(fredbi): IMHO we should rather return some ErrNotImplemented sentinel error and make it explicit.
|
||||
resp, err := h.ForwardToPod("pod1", "/version")
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
})
|
||||
|
||||
t.Run("should collect resources from pods", func(t *testing.T) {
|
||||
envelope, status, err := h.CollectResources(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, envelope, 12*2) // has empty cloud provider, has control plane info
|
||||
require.Len(t, status, 0)
|
||||
|
||||
foundControl, foundProvider := false, false
|
||||
for _, sensed := range envelope {
|
||||
if sensed.Kind == ControlPlaneInfo {
|
||||
foundControl = true
|
||||
}
|
||||
if sensed.Kind == CloudProviderInfo {
|
||||
foundProvider = hasCloudProviderInfo([]hostsensor.HostSensorDataEnvelope{sensed})
|
||||
}
|
||||
}
|
||||
|
||||
require.True(t, foundControl)
|
||||
require.False(t, foundProvider)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should build host sensor with error in response from /version", func(t *testing.T) {
|
||||
k8s := NewKubernetesApiMock(WithNode(mockNode1()),
|
||||
WithPod(mockPod1()),
|
||||
WithPod(mockPod2()),
|
||||
WithResponses(mockResponsesNoCloudProvider()),
|
||||
WithErrorResponse(RestURL{"http", "pod1", "7888", "/version"}), // this endpoint will return an error from this pod
|
||||
)
|
||||
|
||||
h, err := NewHostSensorHandler(k8s, "")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, h)
|
||||
|
||||
t.Run("should initialize host sensor", func(t *testing.T) {
|
||||
require.NoError(t, h.Init(ctx))
|
||||
|
||||
w, err := k8s.KubernetesClient.CoreV1().Pods(h.DaemonSet.Namespace).Watch(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
w.Stop()
|
||||
|
||||
require.Len(t, h.HostSensorPodNames, 2)
|
||||
})
|
||||
|
||||
t.Run("should NOT be able to get version", func(t *testing.T) {
|
||||
_, err := h.GetVersion()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "mock")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should build host sensor with error in response from /kubeletConfigurations", func(t *testing.T) {
|
||||
k8s := NewKubernetesApiMock(WithNode(mockNode1()),
|
||||
WithPod(mockPod1()),
|
||||
WithPod(mockPod2()),
|
||||
WithResponses(mockResponsesNoCloudProvider()),
|
||||
WithErrorResponse(RestURL{"http", "pod1", "7888", "/kubeletConfigurations"}), // this endpoint will return an error from this pod
|
||||
)
|
||||
|
||||
h, err := NewHostSensorHandler(k8s, "")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, h)
|
||||
|
||||
t.Run("should initialize host sensor", func(t *testing.T) {
|
||||
require.NoError(t, h.Init(ctx))
|
||||
|
||||
w, err := k8s.KubernetesClient.CoreV1().Pods(h.DaemonSet.Namespace).Watch(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
w.Stop()
|
||||
|
||||
require.Len(t, h.HostSensorPodNames, 2)
|
||||
})
|
||||
|
||||
t.Run("should collect resources from pods, with some errors", func(t *testing.T) {
|
||||
envelope, status, err := h.CollectResources(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, envelope, 12*2-1) // one resource is missing
|
||||
require.Len(t, status, 0) // error is not reported in status: this is due to the worker pool not bubbling up errors
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should FAIL to build host sensor because there are no nodes", func(t *testing.T) {
|
||||
h, err := NewHostSensorHandler(NewKubernetesApiMock(), "")
|
||||
require.Error(t, err)
|
||||
require.NotNil(t, h)
|
||||
require.Contains(t, err.Error(), "no nodes to scan")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should NOT build host sensor with nil k8s API", func(t *testing.T) {
|
||||
h, err := NewHostSensorHandler(nil, "")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, h)
|
||||
})
|
||||
|
||||
t.Run("with manifest from YAML file", func(t *testing.T) {
|
||||
t.Run("should build host sensor", func(t *testing.T) {
|
||||
k8s := NewKubernetesApiMock(WithNode(mockNode1()), WithPod(mockPod1()), WithPod(mockPod2()), WithResponses(mockResponses()))
|
||||
h, err := NewHostSensorHandler(k8s, filepath.Join(currentDir(), "hostsensor.yaml"))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, h)
|
||||
|
||||
t.Run("should initialize host sensor", func(t *testing.T) {
|
||||
require.NoError(t, h.Init(ctx))
|
||||
|
||||
w, err := k8s.KubernetesClient.CoreV1().Pods(h.DaemonSet.Namespace).Watch(ctx, metav1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
w.Stop()
|
||||
|
||||
require.Len(t, h.HostSensorPodNames, 2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with manifest from invalid YAML file", func(t *testing.T) {
|
||||
t.Run("should NOT build host sensor", func(t *testing.T) {
|
||||
var invalid string
|
||||
t.Run("should create temp file", func(t *testing.T) {
|
||||
file, err := os.CreateTemp("", "*.yaml")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
_ = os.Remove(file.Name())
|
||||
})
|
||||
_, err = file.Write([]byte(" x: 1"))
|
||||
require.NoError(t, err)
|
||||
|
||||
invalid = file.Name()
|
||||
require.NoError(t, file.Close())
|
||||
})
|
||||
|
||||
k8s := NewKubernetesApiMock(WithNode(mockNode1()), WithPod(mockPod1()), WithPod(mockPod2()), WithResponses(mockResponses()))
|
||||
_, err := NewHostSensorHandler(k8s, filepath.Join(currentDir(), invalid))
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
// TODO(test coverage): the following cases are not covered by tests yet.
|
||||
//
|
||||
// * applyYAML fails
|
||||
// * checkPodForEachNode fails, or times out
|
||||
// * non-active namespace
|
||||
// * getPodList fails when GetVersion
|
||||
// * getPodList fails when CollectResources
|
||||
// * error cases that trigger a namespace tear-down
|
||||
// * watch pods with a Delete event
|
||||
// * explicit TearDown()
|
||||
//
|
||||
// Notice that the package doesn't current pass tests with the race detector enabled.
|
||||
}
|
||||
|
||||
func currentDir() string {
|
||||
_, filename, _, _ := runtime.Caller(1)
|
||||
|
||||
return filepath.Dir(filename)
|
||||
}
|
||||
1
core/pkg/hostsensorutils/hostsensorgetfromprod_test.go
Normal file
1
core/pkg/hostsensorutils/hostsensorgetfromprod_test.go
Normal file
@@ -0,0 +1 @@
|
||||
package hostsensorutils
|
||||
587
core/pkg/hostsensorutils/kubernetes_mock_test.go
Normal file
587
core/pkg/hostsensorutils/kubernetes_mock_test.go
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user