From b28f5db353f009318c023f96d2eb623d63d67e24 Mon Sep 17 00:00:00 2001 From: Madhu RAJAGOPAL Date: Wed, 14 Aug 2024 18:52:48 +1000 Subject: [PATCH 01/15] Issue 40: Unit-test for CRD --- pkg/crds/crd_test.go | 82 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 pkg/crds/crd_test.go diff --git a/pkg/crds/crd_test.go b/pkg/crds/crd_test.go new file mode 100644 index 0000000..e594507 --- /dev/null +++ b/pkg/crds/crd_test.go @@ -0,0 +1,82 @@ +package crds + +import ( + "reflect" + "testing" +) + +func TestGetCRDList(t *testing.T) { + tests := []struct { + name string + want []Crd + }{ + { + name: "Correct CRD list", + want: []Crd{ + { + Resource: "apdoslogconfs", + Group: "appprotectdos.f5.com", + Version: "v1beta1", + }, + { + Resource: "apdospolicies", + Group: "appprotectdos.f5.com", + Version: "v1beta1", + }, + { + Resource: "dosprotectedresources", + Group: "appprotectdos.f5.com", + Version: "v1beta1", + }, + { + Resource: "aplogconfs", + Group: "appprotect.f5.com", + Version: "v1beta1", + }, + { + Resource: "appolicies", + Group: "appprotect.f5.com", + Version: "v1beta1", + }, + { + Resource: "apusersigs", + Group: "appprotect.f5.com", + Version: "v1beta1", + }, + { + Resource: "globalconfigurations", + Group: "k8s.nginx.org", + Version: "v1", + }, + { + Resource: "policies", + Group: "k8s.nginx.org", + Version: "v1", + }, + { + Resource: "transportservers", + Group: "k8s.nginx.org", + Version: "v1", + }, + { + Resource: "virtualserverroutes", + Group: "k8s.nginx.org", + Version: "v1", + }, + { + Resource: "virtualservers", + Group: "k8s.nginx.org", + Version: "v1", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetCRDList(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetCRDList() = %v, want %v", got, tt.want) + } + }) + } +} From b1f649c96372bed1f60f9bba6ecc2e8a26673064 Mon Sep 17 00:00:00 2001 From: Madhu RAJAGOPAL Date: Tue, 20 Aug 2024 11:55:10 +1000 Subject: [PATCH 02/15] Issue 40: Unit-test for CRD * This is a exploratory exercise on writing unit-test for data_collector.go and nic_job_list.go * The nic_job_list_test.go works but data_collector_test.go does not * The tests themselves are basic to establish a familiarity in writing tests --- pkg/data_collector/data_collector_test.go | 79 +++++++++++++++++++++++ pkg/jobs/nic_job_list_test.go | 39 +++++++++++ 2 files changed, 118 insertions(+) create mode 100644 pkg/data_collector/data_collector_test.go create mode 100644 pkg/jobs/nic_job_list_test.go diff --git a/pkg/data_collector/data_collector_test.go b/pkg/data_collector/data_collector_test.go new file mode 100644 index 0000000..8b56d32 --- /dev/null +++ b/pkg/data_collector/data_collector_test.go @@ -0,0 +1,79 @@ +package data_collector + +import ( + helmClient "github.com/mittwald/go-helm-client" + crdClient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + metricsClient "k8s.io/metrics/pkg/client/clientset/versioned" + "log" + "os" + "testing" +) + +func TestDataCollector_AllNamespacesExist(t *testing.T) { + type fields struct { + BaseDir string + Namespaces []string + Logger *log.Logger + LogFile *os.File + K8sRestConfig *rest.Config + K8sCoreClientSet *kubernetes.Clientset + K8sCrdClientSet *crdClient.Clientset + K8sMetricsClientSet *metricsClient.Clientset + K8sHelmClientSet map[string]helmClient.Client + } + + var ( + logger *log.Logger = log.New(os.Stdout, "TEST: ", log.LstdFlags) + logFile, _ = os.Create("/path/to/logfile") // Make sure to handle the error in real code. + restConfig *rest.Config = &rest.Config{ /* ... */ } + //coreClientSet *kubernetes.Clientset = /* ... */ + //crdClientSet *crdClient.Clientset = crdClient.NewForConfig(config) + //metricsClientSet *metricsClient.Clientset = /* ... */ + //helmClientSets map[string]helmClient.Client = /* ... */ + ) + + //crdClientSet *crdClient.Clientset = crdClient.NewForConfig(config) + + tests := []struct { + name string + fields fields + want bool + }{ + { + name: "Test Case 1", + fields: fields{ + BaseDir: "/path/to/base", + Namespaces: []string{"default", "kube-system"}, + Logger: logger, + LogFile: logFile, + K8sRestConfig: restConfig, + //K8sCoreClientSet: coreClientSet, + //K8sCrdClientSet: crdClientSet, + //K8sMetricsClientSet: metricsClientSet, + //K8sHelmClientSet: helmClientSets, + }, + want: true, + }, + // You can add more test cases as needed. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &DataCollector{ + BaseDir: tt.fields.BaseDir, + Namespaces: tt.fields.Namespaces, + Logger: tt.fields.Logger, + LogFile: tt.fields.LogFile, + K8sRestConfig: tt.fields.K8sRestConfig, + K8sCoreClientSet: tt.fields.K8sCoreClientSet, + K8sCrdClientSet: tt.fields.K8sCrdClientSet, + K8sMetricsClientSet: tt.fields.K8sMetricsClientSet, + K8sHelmClientSet: tt.fields.K8sHelmClientSet, + } + if got := c.AllNamespacesExist(); got != tt.want { + t.Errorf("AllNamespacesExist() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/jobs/nic_job_list_test.go b/pkg/jobs/nic_job_list_test.go new file mode 100644 index 0000000..d50508c --- /dev/null +++ b/pkg/jobs/nic_job_list_test.go @@ -0,0 +1,39 @@ +package jobs + +import ( + "context" + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + "reflect" + "testing" + "time" +) + +func TestNICJobList(t *testing.T) { + tests := []struct { + name string + want []Job + }{ + // TODO: Add test cases. + { + name: "test-1", + want: []Job{ + { + Name: "pod-list", + Timeout: time.Second * 10, + Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) { + jobResult := JobResult{Files: make(map[string][]byte), Error: nil} + ch <- jobResult + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := NICJobList(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("NICJobList() = %v, want %v", got, tt.want) + } + }) + } +} From 7bb7833a27f90224e106813044f7f4c6b6ae1dcb Mon Sep 17 00:00:00 2001 From: Madhu RAJAGOPAL Date: Wed, 3 Sep 2025 15:27:37 +1200 Subject: [PATCH 03/15] feat: Create unit tests for NIC, NIM, NGF, and NGX --- pkg/crds/crd_test.go | 6 +- pkg/data_collector/data_collector.go | 10 +- pkg/data_collector/data_collector_test.go | 184 ++++++++++++++-------- pkg/jobs/common_job_list.go | 6 +- pkg/jobs/common_job_list_test.go | 158 +++++++++++++++++++ pkg/jobs/job_test.go | 122 ++++++++++++++ pkg/jobs/ngf_job_list_test.go | 161 +++++++++++++++++++ pkg/jobs/ngx_job_list_test.go | 76 +++++++++ pkg/jobs/nic_job_list_test.go | 138 +++++++++++++--- pkg/jobs/nim_job_list_test.go | 163 +++++++++++++++++++ 10 files changed, 926 insertions(+), 98 deletions(-) create mode 100644 pkg/jobs/common_job_list_test.go create mode 100644 pkg/jobs/job_test.go create mode 100644 pkg/jobs/ngf_job_list_test.go create mode 100644 pkg/jobs/ngx_job_list_test.go create mode 100644 pkg/jobs/nim_job_list_test.go diff --git a/pkg/crds/crd_test.go b/pkg/crds/crd_test.go index e594507..d4de52c 100644 --- a/pkg/crds/crd_test.go +++ b/pkg/crds/crd_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestGetCRDList(t *testing.T) { +func TestGetNICCRDList(t *testing.T) { tests := []struct { name string want []Crd @@ -74,8 +74,8 @@ func TestGetCRDList(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := GetCRDList(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetCRDList() = %v, want %v", got, tt.want) + if got := GetNICCRDList(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetNICCRDList() = %v, want %v", got, tt.want) } }) } diff --git a/pkg/data_collector/data_collector.go b/pkg/data_collector/data_collector.go index 0df57fa..186dea4 100644 --- a/pkg/data_collector/data_collector.go +++ b/pkg/data_collector/data_collector.go @@ -52,12 +52,14 @@ type DataCollector struct { Logger *log.Logger LogFile *os.File K8sRestConfig *rest.Config - K8sCoreClientSet *kubernetes.Clientset + K8sCoreClientSet kubernetes.Interface //*kubernetes.Clientset K8sCrdClientSet *crdClient.Clientset K8sMetricsClientSet *metricsClient.Clientset K8sHelmClientSet map[string]helmClient.Client ExcludeDBData bool ExcludeTimeSeriesData bool + PodExecutor func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) + QueryCRD func(crd crds.Crd, namespace string, ctx context.Context) ([]byte, error) } func NewDataCollector(collector *DataCollector) error { @@ -87,6 +89,8 @@ func NewDataCollector(collector *DataCollector) error { collector.LogFile = logFile collector.Logger = log.New(logFile, "", log.LstdFlags|log.LUTC|log.Lmicroseconds|log.Lshortfile) collector.K8sHelmClientSet = make(map[string]helmClient.Client) + collector.PodExecutor = collector.RealPodExecutor + collector.QueryCRD = collector.RealQueryCRD //Initialize clients collector.K8sRestConfig = config @@ -199,7 +203,7 @@ func (c *DataCollector) WrapUp(product string) (string, error) { return tarballName, nil } -func (c *DataCollector) PodExecutor(namespace string, pod string, container string, command []string, ctx context.Context) ([]byte, error) { +func (c *DataCollector) RealPodExecutor(namespace string, pod string, container string, command []string, ctx context.Context) ([]byte, error) { req := c.K8sCoreClientSet.CoreV1().RESTClient().Post(). Namespace(namespace). Resource("pods"). @@ -232,7 +236,7 @@ func (c *DataCollector) PodExecutor(namespace string, pod string, container stri } } -func (c *DataCollector) QueryCRD(crd crds.Crd, namespace string, ctx context.Context) ([]byte, error) { +func (c *DataCollector) RealQueryCRD(crd crds.Crd, namespace string, ctx context.Context) ([]byte, error) { schemeGroupVersion := schema.GroupVersion{Group: crd.Group, Version: crd.Version} negotiatedSerializer := scheme.Codecs.WithoutConversion() diff --git a/pkg/data_collector/data_collector_test.go b/pkg/data_collector/data_collector_test.go index 8b56d32..badc500 100644 --- a/pkg/data_collector/data_collector_test.go +++ b/pkg/data_collector/data_collector_test.go @@ -1,79 +1,133 @@ package data_collector import ( - helmClient "github.com/mittwald/go-helm-client" - crdClient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - metricsClient "k8s.io/metrics/pkg/client/clientset/versioned" + "bytes" + "context" + "io" "log" "os" + "path/filepath" "testing" + + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/crds" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/rest" ) -func TestDataCollector_AllNamespacesExist(t *testing.T) { - type fields struct { - BaseDir string - Namespaces []string - Logger *log.Logger - LogFile *os.File - K8sRestConfig *rest.Config - K8sCoreClientSet *kubernetes.Clientset - K8sCrdClientSet *crdClient.Clientset - K8sMetricsClientSet *metricsClient.Clientset - K8sHelmClientSet map[string]helmClient.Client +func TestNewDataCollector_Success(t *testing.T) { + dc := &DataCollector{Namespaces: []string{"default"}} + err := NewDataCollector(dc) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if dc.BaseDir == "" { + t.Error("BaseDir should be set") + } + if dc.Logger == nil { + t.Error("Logger should be set") + } + if dc.LogFile == nil { + t.Error("LogFile should be set") + } + if dc.K8sCoreClientSet == nil { + t.Error("K8sCoreClientSet should be set") + } + if dc.K8sCrdClientSet == nil { + t.Error("K8sCrdClientSet should be set") + } + if dc.K8sMetricsClientSet == nil { + t.Error("K8sMetricsClientSet should be set") } + if dc.K8sHelmClientSet == nil { + t.Error("K8sHelmClientSet should be set") + } +} - var ( - logger *log.Logger = log.New(os.Stdout, "TEST: ", log.LstdFlags) - logFile, _ = os.Create("/path/to/logfile") // Make sure to handle the error in real code. - restConfig *rest.Config = &rest.Config{ /* ... */ } - //coreClientSet *kubernetes.Clientset = /* ... */ - //crdClientSet *crdClient.Clientset = crdClient.NewForConfig(config) - //metricsClientSet *metricsClient.Clientset = /* ... */ - //helmClientSets map[string]helmClient.Client = /* ... */ - ) +func TestWrapUp_CreatesTarball(t *testing.T) { + tmpDir := t.TempDir() + logFile, _ := os.Create(filepath.Join(tmpDir, "supportpkg.log")) + dc := &DataCollector{ + BaseDir: tmpDir, + LogFile: logFile, + Logger: log.New(io.Discard, "", 0), + } + product := "nginx" + tarball, err := dc.WrapUp(product) + if err != nil { + t.Fatalf("WrapUp failed: %v", err) + } + if _, err := os.Stat(tarball); err != nil { + t.Errorf("tarball not created: %v", err) + } + _ = os.Remove(tarball) +} - //crdClientSet *crdClient.Clientset = crdClient.NewForConfig(config) +func TestRealPodExecutor_ReturnsOutput(t *testing.T) { + dc := &DataCollector{ + K8sCoreClientSet: fake.NewSimpleClientset(), + K8sRestConfig: &rest.Config{}, + } + // Replace RealPodExecutor with a mock for testing + dc.PodExecutor = func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { + return []byte("output"), nil + } + out, err := dc.PodExecutor("default", "pod", "container", []string{"echo", "hello"}, context.TODO()) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if !bytes.Contains(out, []byte("output")) { + t.Errorf("expected output, got %s", string(out)) + } +} + +func TestRealQueryCRD_ReturnsErrorOnInvalidConfig(t *testing.T) { + dc := &DataCollector{ + K8sRestConfig: &rest.Config{}, + } + crd := crds.Crd{Group: "test", Version: "v1", Resource: "foos"} + _, err := dc.RealQueryCRD(crd, "default", context.TODO()) + if err == nil { + t.Error("expected error for invalid config") + } +} + +func TestAllNamespacesExist_AllExist(t *testing.T) { + client := fake.NewSimpleClientset(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "default"}}) + dc := &DataCollector{ + Namespaces: []string{"default"}, + K8sCoreClientSet: client, + Logger: log.New(io.Discard, "", 0), + } + if !dc.AllNamespacesExist() { + t.Error("expected all namespaces to exist") + } +} + +func TestAllNamespacesExist_NotExist(t *testing.T) { + client := fake.NewSimpleClientset() + dc := &DataCollector{ + Namespaces: []string{"missing"}, + K8sCoreClientSet: client, + Logger: log.New(io.Discard, "", 0), + } + if dc.AllNamespacesExist() { + t.Error("expected namespaces to not exist") + } +} - tests := []struct { - name string - fields fields - want bool - }{ - { - name: "Test Case 1", - fields: fields{ - BaseDir: "/path/to/base", - Namespaces: []string{"default", "kube-system"}, - Logger: logger, - LogFile: logFile, - K8sRestConfig: restConfig, - //K8sCoreClientSet: coreClientSet, - //K8sCrdClientSet: crdClientSet, - //K8sMetricsClientSet: metricsClientSet, - //K8sHelmClientSet: helmClientSets, - }, - want: true, - }, - // You can add more test cases as needed. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := &DataCollector{ - BaseDir: tt.fields.BaseDir, - Namespaces: tt.fields.Namespaces, - Logger: tt.fields.Logger, - LogFile: tt.fields.LogFile, - K8sRestConfig: tt.fields.K8sRestConfig, - K8sCoreClientSet: tt.fields.K8sCoreClientSet, - K8sCrdClientSet: tt.fields.K8sCrdClientSet, - K8sMetricsClientSet: tt.fields.K8sMetricsClientSet, - K8sHelmClientSet: tt.fields.K8sHelmClientSet, - } - if got := c.AllNamespacesExist(); got != tt.want { - t.Errorf("AllNamespacesExist() = %v, want %v", got, tt.want) - } - }) +func TestWrapUp_ErrorOnLogFileClose(t *testing.T) { + tmpDir := t.TempDir() + logFile, _ := os.Create(filepath.Join(tmpDir, "supportpkg.log")) + logFile.Close() // Already closed + dc := &DataCollector{ + BaseDir: tmpDir, + LogFile: logFile, + Logger: log.New(io.Discard, "", 0), + } + _, err := dc.WrapUp("nginx") + if err == nil { + t.Error("expected error on closing already closed log file") } } diff --git a/pkg/jobs/common_job_list.go b/pkg/jobs/common_job_list.go index f2e0aa5..a59be6e 100644 --- a/pkg/jobs/common_job_list.go +++ b/pkg/jobs/common_job_list.go @@ -146,7 +146,7 @@ func CommonJobList() []Job { Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) { jobResult := JobResult{Files: make(map[string][]byte), Error: nil} for _, namespace := range dc.Namespaces { - result, err := dc.K8sCoreClientSet.DiscoveryClient.ServerPreferredResources() + result, err := dc.K8sCoreClientSet.Discovery().ServerPreferredResources() if err != nil { dc.Logger.Printf("\tCould not retrieve API resources list %s: %v\n", namespace, err) } else { @@ -163,7 +163,7 @@ func CommonJobList() []Job { Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) { jobResult := JobResult{Files: make(map[string][]byte), Error: nil} for _, namespace := range dc.Namespaces { - result, err := dc.K8sCoreClientSet.DiscoveryClient.ServerGroups() + result, err := dc.K8sCoreClientSet.Discovery().ServerGroups() if err != nil { dc.Logger.Printf("\tCould not retrieve API versions list %s: %v\n", namespace, err) } else { @@ -367,7 +367,7 @@ func CommonJobList() []Job { Timeout: time.Second * 10, Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) { jobResult := JobResult{Files: make(map[string][]byte), Error: nil} - result, err := dc.K8sCoreClientSet.ServerVersion() + result, err := dc.K8sCoreClientSet.Discovery().ServerVersion() if err != nil { dc.Logger.Printf("\tCould not retrieve server version: %v\n", err) } else { diff --git a/pkg/jobs/common_job_list_test.go b/pkg/jobs/common_job_list_test.go new file mode 100644 index 0000000..9a938dd --- /dev/null +++ b/pkg/jobs/common_job_list_test.go @@ -0,0 +1,158 @@ +package jobs + +import ( + "context" + "io" + "log" + "path/filepath" + "testing" + "time" + + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + "k8s.io/client-go/kubernetes/fake" +) + +// helper creates int32 ptr +func i32(v int32) *int32 { return &v } + +func setupDataCollector(t *testing.T) *data_collector.DataCollector { + t.Helper() + + tmpDir := t.TempDir() + + // Seed fake objects (namespace default implied by metadata) + objs := []runtime.Object{ + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-1", Namespace: "default"}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "c1", Image: "nginx:latest"}}, + }, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "svc-1", Namespace: "default"}, + Spec: corev1.ServiceSpec{Selector: map[string]string{"app": "demo"}}, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "dep-1", Namespace: "default"}, + Spec: appsv1.DeploymentSpec{ + Replicas: i32(1), + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "demo"}}, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "demo"}}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "dep-c1", Image: "nginx:latest"}}, + }, + }, + }, + }, + &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{Name: "role-1", Namespace: "default"}, + Rules: []rbacv1.PolicyRule{{APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"get"}}}, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "cm-1", Namespace: "default"}, + Data: map[string]string{"k": "v"}, + }, + } + + client := fake.NewSimpleClientset(objs...) + + return &data_collector.DataCollector{ + BaseDir: tmpDir, + Namespaces: []string{"default"}, + Logger: log.New(io.Discard, "", 0), + K8sCoreClientSet: client, + // Leave other client sets nil; we will not execute jobs that depend on them in this focused test. + } +} + +func TestCommonJobList_SelectedJobsProduceFiles(t *testing.T) { + dc := setupDataCollector(t) + + // Jobs we explicitly validate (keep focused; others require additional fake clients) + targetJobs := map[string]struct{}{ + "pod-list": {}, + "service-list": {}, + "deployment-list": {}, + "roles-list": {}, + "configmap-list": {}, + } + + jobList := CommonJobList() + + for _, job := range jobList { + if _, ok := targetJobs[job.Name]; !ok { + continue + } + + ch := make(chan JobResult, 1) + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + go job.Execute(dc, ctx, ch) + + select { + case res := <-ch: + if res.Error != nil { + t.Fatalf("job %s returned unexpected error: %v", job.Name, res.Error) + } + if len(res.Files) == 0 { + t.Fatalf("job %s produced no files", job.Name) + } + // Basic path sanity + non-empty content + for path, content := range res.Files { + if len(content) == 0 { + t.Fatalf("job %s file %s has empty content", job.Name, path) + } + if !filepath.HasPrefix(path, dc.BaseDir) { // acceptable here; test code (Go <1.22 deprecation not critical) + t.Fatalf("job %s file path %s does not start with basedir %s", job.Name, path, dc.BaseDir) + } + } + case <-ctx.Done(): + t.Fatalf("job %s timed out", job.Name) + } + } +} + +func TestCommonJobList_PodListJSONKeyPresence(t *testing.T) { + dc := setupDataCollector(t) + var podListJob *Job + for i, j := range CommonJobList() { + if j.Name == "pod-list" { + podListJob = &CommonJobList()[i] + break + } + } + if podListJob == nil { + t.Fatalf("pod-list job not found") + } + + ch := make(chan JobResult, 1) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + go podListJob.Execute(dc, ctx, ch) + + res := <-ch + if res.Error != nil { + t.Fatalf("pod-list job returned error: %v", res.Error) + } + if len(res.Files) != 1 { + t.Fatalf("expected 1 file from pod-list job, got %d", len(res.Files)) + } + for path, content := range res.Files { + if filepath.Base(path) != "pods.json" { + t.Fatalf("expected pods.json file, got %s", path) + } + // Quick check JSON starts with '{' (marshaled list) or '[' depending on structure (PodList marshals to object) + if len(content) == 0 || content[0] != '{' { + t.Fatalf("unexpected JSON content in %s", path) + } + } +} diff --git a/pkg/jobs/job_test.go b/pkg/jobs/job_test.go new file mode 100644 index 0000000..e695d59 --- /dev/null +++ b/pkg/jobs/job_test.go @@ -0,0 +1,122 @@ +package jobs + +import ( + "context" + "errors" + "io" + "log" + "os" + "path/filepath" + "testing" + "time" + + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" +) + +// mockLogger implements the minimal Printf interface for testing +// type mockLogger struct{} + +// func (l *mockLogger) Printf(format string, v ...interface{}) {} + +// Test successful job execution and file writing +func TestJobCollect_Success(t *testing.T) { + tmpDir := t.TempDir() + dc := &data_collector.DataCollector{ + BaseDir: tmpDir, + Logger: log.New(io.Discard, "", 0), + } + + job := Job{ + Name: "test-job", + Timeout: time.Second, + Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) { + files := map[string][]byte{ + filepath.Join(dc.BaseDir, "output.txt"): []byte("hello world"), + } + ch <- JobResult{Files: files} + }, + } + + err, skipped := job.Collect(dc) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if skipped { + t.Fatalf("expected not skipped") + } + // Check file was written + content, err := os.ReadFile(filepath.Join(tmpDir, "output.txt")) + if err != nil { + t.Fatalf("file not written: %v", err) + } + if string(content) != "hello world" { + t.Fatalf("unexpected file content: %s", string(content)) + } +} + +// Test job skipped scenario +func TestJobCollect_Skipped(t *testing.T) { + dc := &data_collector.DataCollector{ + BaseDir: t.TempDir(), + Logger: log.New(io.Discard, "", 0), + } + job := Job{ + Name: "skip-job", + Timeout: time.Second, + Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) { + ch <- JobResult{Skipped: true} + }, + } + err, skipped := job.Collect(dc) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if !skipped { + t.Fatalf("expected skipped") + } +} + +// Test job error scenario +func TestJobCollect_Error(t *testing.T) { + dc := &data_collector.DataCollector{ + BaseDir: t.TempDir(), + Logger: log.New(io.Discard, "", 0), + } + job := Job{ + Name: "error-job", + Timeout: time.Second, + Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) { + ch <- JobResult{Error: errors.New("fail")} + }, + } + err, skipped := job.Collect(dc) + if err == nil || err.Error() != "fail" { + t.Fatalf("expected error 'fail', got %v", err) + } + if skipped { + t.Fatalf("expected not skipped") + } +} + +// Test job timeout scenario +func TestJobCollect_Timeout(t *testing.T) { + dc := &data_collector.DataCollector{ + BaseDir: t.TempDir(), + Logger: log.New(io.Discard, "", 0), + } + job := Job{ + Name: "timeout-job", + Timeout: time.Millisecond * 10, + Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) { + time.Sleep(time.Second) + ch <- JobResult{} + }, + } + err, skipped := job.Collect(dc) + if err == nil { + t.Fatalf("expected timeout error, got nil") + } + if skipped { + t.Fatalf("expected not skipped") + } +} diff --git a/pkg/jobs/ngf_job_list_test.go b/pkg/jobs/ngf_job_list_test.go new file mode 100644 index 0000000..791b5c7 --- /dev/null +++ b/pkg/jobs/ngf_job_list_test.go @@ -0,0 +1,161 @@ +package jobs + +import ( + "context" + "io" + "log" + "strings" + "testing" + + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func TestNGFJobList(t *testing.T) { + jobs := NGFJobList() + if len(jobs) == 0 { + t.Error("expected jobs to be returned") + } + + expectedJobs := []string{"exec-nginx-gateway-version", "exec-nginx-t", "crd-objects"} + if len(jobs) != len(expectedJobs) { + t.Errorf("expected %d jobs, got %d", len(expectedJobs), len(jobs)) + } + + for i, job := range jobs { + if job.Name != expectedJobs[i] { + t.Errorf("expected job name %s, got %s", expectedJobs[i], job.Name) + } + if job.Execute == nil { + t.Errorf("job %s should have Execute function", job.Name) + } + if job.Timeout == 0 { + t.Errorf("job %s should have timeout set", job.Name) + } + } +} + +func TestNGFJobExecNginxGatewayVersion(t *testing.T) { + tmpDir := t.TempDir() + client := fake.NewSimpleClientset(&corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx-gateway-test-pod", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "nginx-gateway"}, + }, + }, + }) + + // TODO: Add test logic for exec-nginx-t job here. + + // TODO: Add test logic for exec-nginx-t job here. + + dc := &data_collector.DataCollector{ + BaseDir: tmpDir, + Namespaces: []string{"default"}, + K8sCoreClientSet: client, + Logger: log.New(io.Discard, "", 0), + PodExecutor: func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { + return []byte("gateway version output"), nil + }, + } + + jobs := NGFJobList() + var versionJob Job + for _, job := range jobs { + if job.Name == "exec-nginx-gateway-version" { + versionJob = job + break + } + } + + ch := make(chan JobResult, 1) + ctx := context.Background() + + versionJob.Execute(dc, ctx, ch) + result := <-ch + + if result.Error != nil { + t.Errorf("expected no error, got %v", result.Error) + } + + if len(result.Files) == 0 { + t.Error("expected files to be created") + } + + found := false + for filename := range result.Files { + if strings.Contains(filename, "nginx-gateway-version.txt") { + found = true + break + } + } + if !found { + t.Error("expected nginx-gateway-version.txt file to be created") + } +} + +func TestNGFJobExecNginxT(t *testing.T) { + tmpDir := t.TempDir() + client := fake.NewSimpleClientset(&corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx-gateway-test-pod", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "nginx"}, + }, + }, + }) + + // TODO: Add test logic for exec-nginx-t job here. + dc := &data_collector.DataCollector{ + BaseDir: tmpDir, + Namespaces: []string{"default"}, + K8sCoreClientSet: client, + Logger: log.New(io.Discard, "", 0), + PodExecutor: func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { + return []byte("nginx -t output"), nil + }, + } + + jobs := NGFJobList() + var tJob Job + for _, job := range jobs { + if job.Name == "exec-nginx-t" { + tJob = job + break + } + } + + ch := make(chan JobResult, 1) + ctx := context.Background() + + tJob.Execute(dc, ctx, ch) + result := <-ch + + if result.Error != nil { + t.Errorf("expected no error, got %v", result.Error) + } + + if len(result.Files) == 0 { + t.Error("expected files to be created") + } + + found := false + for filename := range result.Files { + if strings.Contains(filename, "nginx-t.txt") { + found = true + break + } + } + if !found { + t.Error("expected nginx-t.txt file to be created") + } +} diff --git a/pkg/jobs/ngx_job_list_test.go b/pkg/jobs/ngx_job_list_test.go new file mode 100644 index 0000000..46c88b9 --- /dev/null +++ b/pkg/jobs/ngx_job_list_test.go @@ -0,0 +1,76 @@ +package jobs + +import ( + "context" + "io" + "log" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func TestNGXJobList_ExecNginxT(t *testing.T) { + tmpDir := t.TempDir() + dc := &data_collector.DataCollector{ + BaseDir: tmpDir, + Logger: log.New(io.Discard, "", 0), + Namespaces: []string{"default"}, + } + + // Create a fake pod named "nginx-123" in the "default" namespace + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nginx-123", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "nginx"}, + }, + }, + } + dc.K8sCoreClientSet = fake.NewSimpleClientset(pod) + + // Mock PodExecutor + dc.PodExecutor = func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { + return []byte("nginx -T output"), nil + } + + jobList := NGXJobList() + if len(jobList) != 1 { + t.Fatalf("expected 1 job, got %d", len(jobList)) + } + job := jobList[0] + ch := make(chan JobResult, 1) + go job.Execute(dc, context.Background(), ch) + select { + case result := <-ch: + if result.Error != nil { + t.Fatalf("unexpected error: %v", result.Error) + } + found := false + for file, content := range result.Files { + if !strings.HasSuffix(file, "__nginx-t.txt") { + t.Errorf("unexpected file name: %s", file) + } + if string(content) != "nginx -T output" { + t.Errorf("unexpected file content: %s", string(content)) + } + if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(tmpDir)) { + t.Errorf("file path %s does not start with tmpDir %s", file, tmpDir) + } + found = true + } + if !found { + t.Errorf("no output file created by job") + } + case <-time.After(time.Second): + t.Fatal("job execution timed out") + } +} diff --git a/pkg/jobs/nic_job_list_test.go b/pkg/jobs/nic_job_list_test.go index d50508c..d32a0b5 100644 --- a/pkg/jobs/nic_job_list_test.go +++ b/pkg/jobs/nic_job_list_test.go @@ -2,38 +2,128 @@ package jobs import ( "context" - "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" - "reflect" + "encoding/json" + "io" + "log" + "path/filepath" + "strings" "testing" "time" + + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/crds" + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" ) -func TestNICJobList(t *testing.T) { - tests := []struct { - name string - want []Job - }{ - // TODO: Add test cases. - { - name: "test-1", - want: []Job{ - { - Name: "pod-list", - Timeout: time.Second * 10, - Execute: func(dc *data_collector.DataCollector, ctx context.Context, ch chan JobResult) { - jobResult := JobResult{Files: make(map[string][]byte), Error: nil} - ch <- jobResult - }, - }, +// mockPodExecutor simulates PodExecutor for testing +func mockPodExecutor(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { + return []byte("mock-output"), nil +} + +// mockQueryCRD simulates QueryCRD for testing +func mockQueryCRD(crd crds.Crd, namespace string, ctx context.Context) ([]byte, error) { + return json.Marshal(map[string]string{"kind": crd.Resource}) +} + +// Mock K8sCoreClientSet to return fake pods +type FakePodInterface struct { + ListFunc func(ctx context.Context, opts metav1.ListOptions) (*corev1.PodList, error) +} + +func (f *FakePodInterface) List(ctx context.Context, opts metav1.ListOptions) (*corev1.PodList, error) { + return f.ListFunc(ctx, opts) +} + +type FakeCoreClientSet struct { + PodsFunc func(namespace string) *FakePodInterface +} + +func (f *FakeCoreClientSet) Pods(namespace string) *FakePodInterface { + return f.PodsFunc(namespace) +} + +func TestNICJobList_ExecJobs(t *testing.T) { + tmpDir := t.TempDir() + dc := &data_collector.DataCollector{ + BaseDir: tmpDir, + Logger: log.New(io.Discard, "", 0), + Namespaces: []string{"test-ns"}, + } + // Mock PodExecutor and QueryCRD + dc.PodExecutor = mockPodExecutor + dc.QueryCRD = mockQueryCRD + + // Use a real or fake clientset (kubernetes.Interface) + dc.K8sCoreClientSet = fake.NewSimpleClientset(&corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "ingress-pod", Namespace: "test-ns"}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "container1"}, }, }, + }) + + jobList := NICJobList() + for _, job := range jobList { + ch := make(chan JobResult, 1) + go job.Execute(dc, context.Background(), ch) + select { + case result := <-ch: + if result.Error != nil { + t.Errorf("Job %s returned error: %v", job.Name, result.Error) + } + for file, content := range result.Files { + if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(tmpDir)) { + t.Errorf("File path %s does not start with tmpDir", file) + } + if len(content) == 0 { + t.Errorf("File %s has empty content", file) + } + } + case <-time.After(time.Second): + t.Errorf("Job %s timed out", job.Name) + } + } +} + +func TestNICJobList_CRDObjects(t *testing.T) { + tmpDir := t.TempDir() + dc := &data_collector.DataCollector{ + BaseDir: tmpDir, + Logger: log.New(io.Discard, "", 0), + Namespaces: []string{"test-ns"}, } + dc.QueryCRD = mockQueryCRD - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := NICJobList(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("NICJobList() = %v, want %v", got, tt.want) + jobList := NICJobList() + var found bool + for _, job := range jobList { + if job.Name == "crd-objects" { + found = true + ch := make(chan JobResult, 1) + go job.Execute(dc, context.Background(), ch) + select { + case result := <-ch: + if result.Error != nil { + t.Errorf("CRD job returned error: %v", result.Error) + } + for file, content := range result.Files { + if !filepath.HasPrefix(file, tmpDir) { + t.Errorf("File path %s does not start with tmpDir", file) + } + var out map[string]interface{} + if err := json.Unmarshal(content, &out); err != nil { + t.Errorf("Invalid JSON in file %s: %v", file, err) + } + } + case <-time.After(time.Second): + t.Errorf("CRD job timed out") } - }) + } + } + if !found { + t.Errorf("crd-objects job not found in NICJobList") } } diff --git a/pkg/jobs/nim_job_list_test.go b/pkg/jobs/nim_job_list_test.go new file mode 100644 index 0000000..6cf32c9 --- /dev/null +++ b/pkg/jobs/nim_job_list_test.go @@ -0,0 +1,163 @@ +package jobs + +import ( + "context" + "io" + "log" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" +) + +func TestNIMJobList_ExecJobs(t *testing.T) { + tmpDir := t.TempDir() + dc := &data_collector.DataCollector{ + BaseDir: tmpDir, + Logger: log.New(io.Discard, "", 0), + Namespaces: []string{"default"}, + } + + // Create fake pods for each job type + pods := []*corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "apigw-123", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "apigw"}}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "clickhouse-456", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "clickhouse-server"}}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "core-789", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "core"}}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "dpm-101", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "dpm"}}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "integrations-102", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "integrations"}}, + }, + }, + } + + objects := make([]runtime.Object, len(pods)) + for i, pod := range pods { + objects[i] = pod + } + dc.K8sCoreClientSet = fake.NewSimpleClientset(objects...) + + // Mock PodExecutor to return predictable output + dc.PodExecutor = func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { + return []byte(strings.Join(command, " ")), nil + } + + // Run all jobs in NIMJobList + for _, job := range NIMJobList() { + ch := make(chan JobResult, 1) + go job.Execute(dc, context.Background(), ch) + select { + case result := <-ch: + if result.Error != nil { + t.Errorf("Job %s returned error: %v", job.Name, result.Error) + } + for file, content := range result.Files { + if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(tmpDir)) { + t.Errorf("File path %s does not start with tmpDir %s", file, tmpDir) + } + if len(content) == 0 { + t.Errorf("File %s has empty content", file) + } + } + case <-time.After(2 * time.Second): + t.Errorf("Job %s timed out", job.Name) + } + } +} + +func TestNIMJobList_ExcludeFlags(t *testing.T) { + tmpDir := t.TempDir() + dc := &data_collector.DataCollector{ + BaseDir: tmpDir, + Logger: log.New(io.Discard, "", 0), + Namespaces: []string{"default"}, + K8sCoreClientSet: fake.NewSimpleClientset(&corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "clickhouse-456", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "clickhouse-server"}}, + }, + }), + PodExecutor: func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { + return []byte("output"), nil + }, + } + + // Test ExcludeTimeSeriesData for exec-clickhouse-data + dc.ExcludeTimeSeriesData = true + for _, job := range NIMJobList() { + if job.Name == "exec-clickhouse-data" { + ch := make(chan JobResult, 1) + go job.Execute(dc, context.Background(), ch) + select { + case result := <-ch: + if !result.Skipped { + t.Errorf("Expected job to be skipped when ExcludeTimeSeriesData is true") + } + case <-time.After(time.Second): + t.Fatal("Job exec-clickhouse-data timed out") + } + } + } + + // Test ExcludeDBData for exec-dqlite-dump + dc.ExcludeDBData = true + for _, job := range NIMJobList() { + if job.Name == "exec-dqlite-dump" { + ch := make(chan JobResult, 1) + go job.Execute(dc, context.Background(), ch) + select { + case result := <-ch: + if !result.Skipped { + t.Errorf("Expected job to be skipped when ExcludeDBData is true") + } + case <-time.After(time.Second): + t.Fatal("Job exec-dqlite-dump timed out") + } + } + } +} From 33dabbd993f25e1ffae9d787a7fab1fc9a19e2d7 Mon Sep 17 00:00:00 2001 From: Madhu RAJAGOPAL Date: Wed, 3 Sep 2025 17:28:44 +1200 Subject: [PATCH 04/15] Chore: Update to reflect changes in return parameters --- pkg/jobs/job_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/jobs/job_test.go b/pkg/jobs/job_test.go index e695d59..c0cfd6c 100644 --- a/pkg/jobs/job_test.go +++ b/pkg/jobs/job_test.go @@ -37,7 +37,7 @@ func TestJobCollect_Success(t *testing.T) { }, } - err, skipped := job.Collect(dc) + err, skipped, _ := job.Collect(dc) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -67,7 +67,7 @@ func TestJobCollect_Skipped(t *testing.T) { ch <- JobResult{Skipped: true} }, } - err, skipped := job.Collect(dc) + err, skipped, _ := job.Collect(dc) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -89,7 +89,7 @@ func TestJobCollect_Error(t *testing.T) { ch <- JobResult{Error: errors.New("fail")} }, } - err, skipped := job.Collect(dc) + err, skipped, _ := job.Collect(dc) if err == nil || err.Error() != "fail" { t.Fatalf("expected error 'fail', got %v", err) } @@ -112,7 +112,7 @@ func TestJobCollect_Timeout(t *testing.T) { ch <- JobResult{} }, } - err, skipped := job.Collect(dc) + err, skipped, _ := job.Collect(dc) if err == nil { t.Fatalf("expected timeout error, got nil") } From d53e7ff3c06553431806fef149af543379b0831b Mon Sep 17 00:00:00 2001 From: Madhu RAJAGOPAL Date: Thu, 4 Sep 2025 11:47:25 +1200 Subject: [PATCH 05/15] chore: replace deprecated routine filepath.HasPrefix with strings.HasPrefix --- pkg/jobs/common_job_list_test.go | 3 ++- pkg/jobs/nic_job_list_test.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/jobs/common_job_list_test.go b/pkg/jobs/common_job_list_test.go index 9a938dd..b9bb0f4 100644 --- a/pkg/jobs/common_job_list_test.go +++ b/pkg/jobs/common_job_list_test.go @@ -5,6 +5,7 @@ import ( "io" "log" "path/filepath" + "strings" "testing" "time" @@ -111,7 +112,7 @@ func TestCommonJobList_SelectedJobsProduceFiles(t *testing.T) { if len(content) == 0 { t.Fatalf("job %s file %s has empty content", job.Name, path) } - if !filepath.HasPrefix(path, dc.BaseDir) { // acceptable here; test code (Go <1.22 deprecation not critical) + if !strings.HasPrefix(filepath.ToSlash(path), filepath.ToSlash(dc.BaseDir)) { t.Fatalf("job %s file path %s does not start with basedir %s", job.Name, path, dc.BaseDir) } } diff --git a/pkg/jobs/nic_job_list_test.go b/pkg/jobs/nic_job_list_test.go index d32a0b5..888ed92 100644 --- a/pkg/jobs/nic_job_list_test.go +++ b/pkg/jobs/nic_job_list_test.go @@ -110,7 +110,7 @@ func TestNICJobList_CRDObjects(t *testing.T) { t.Errorf("CRD job returned error: %v", result.Error) } for file, content := range result.Files { - if !filepath.HasPrefix(file, tmpDir) { + if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(tmpDir)) { t.Errorf("File path %s does not start with tmpDir", file) } var out map[string]interface{} From 2d9225d53baa331be683a6ac9bef1e372e61d009 Mon Sep 17 00:00:00 2001 From: Madhu Rajagopal Date: Thu, 4 Sep 2025 16:36:23 +1200 Subject: [PATCH 06/15] Update pkg/jobs/ngf_job_list_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/jobs/ngf_job_list_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/jobs/ngf_job_list_test.go b/pkg/jobs/ngf_job_list_test.go index 791b5c7..5055ead 100644 --- a/pkg/jobs/ngf_job_list_test.go +++ b/pkg/jobs/ngf_job_list_test.go @@ -51,10 +51,6 @@ func TestNGFJobExecNginxGatewayVersion(t *testing.T) { }, }) - // TODO: Add test logic for exec-nginx-t job here. - - // TODO: Add test logic for exec-nginx-t job here. - dc := &data_collector.DataCollector{ BaseDir: tmpDir, Namespaces: []string{"default"}, From ecb3c6293b13515a69bd1f0801012ce5b50aed53 Mon Sep 17 00:00:00 2001 From: Madhu Rajagopal Date: Thu, 4 Sep 2025 16:38:52 +1200 Subject: [PATCH 07/15] Update pkg/jobs/ngf_job_list_test.go Remove redundant TODO. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/jobs/ngf_job_list_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/jobs/ngf_job_list_test.go b/pkg/jobs/ngf_job_list_test.go index 5055ead..f2059fa 100644 --- a/pkg/jobs/ngf_job_list_test.go +++ b/pkg/jobs/ngf_job_list_test.go @@ -110,7 +110,6 @@ func TestNGFJobExecNginxT(t *testing.T) { }, }) - // TODO: Add test logic for exec-nginx-t job here. dc := &data_collector.DataCollector{ BaseDir: tmpDir, Namespaces: []string{"default"}, From 6d7b09dcbf2989c6fc18ad977af34143f806bc6d Mon Sep 17 00:00:00 2001 From: Madhu Rajagopal Date: Thu, 4 Sep 2025 16:42:21 +1200 Subject: [PATCH 08/15] Update pkg/data_collector/data_collector.go Remove redundant comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/data_collector/data_collector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/data_collector/data_collector.go b/pkg/data_collector/data_collector.go index 34407d3..047f705 100644 --- a/pkg/data_collector/data_collector.go +++ b/pkg/data_collector/data_collector.go @@ -54,7 +54,7 @@ type DataCollector struct { Logger *log.Logger LogFile *os.File K8sRestConfig *rest.Config - K8sCoreClientSet kubernetes.Interface //*kubernetes.Clientset + K8sCoreClientSet kubernetes.Interface K8sCrdClientSet *crdClient.Clientset K8sMetricsClientSet *metricsClient.Clientset K8sHelmClientSet map[string]helmClient.Client From 6ef2f3e4a54020b557571f41defb9f32f9131eb4 Mon Sep 17 00:00:00 2001 From: Madhu Rajagopal Date: Thu, 4 Sep 2025 16:43:19 +1200 Subject: [PATCH 09/15] Update pkg/jobs/nic_job_list_test.go Removed custom fake types; use fake.NewSimpleClientset instead Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/jobs/nic_job_list_test.go | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/pkg/jobs/nic_job_list_test.go b/pkg/jobs/nic_job_list_test.go index 888ed92..4ed634d 100644 --- a/pkg/jobs/nic_job_list_test.go +++ b/pkg/jobs/nic_job_list_test.go @@ -27,23 +27,7 @@ func mockQueryCRD(crd crds.Crd, namespace string, ctx context.Context) ([]byte, return json.Marshal(map[string]string{"kind": crd.Resource}) } -// Mock K8sCoreClientSet to return fake pods -type FakePodInterface struct { - ListFunc func(ctx context.Context, opts metav1.ListOptions) (*corev1.PodList, error) -} - -func (f *FakePodInterface) List(ctx context.Context, opts metav1.ListOptions) (*corev1.PodList, error) { - return f.ListFunc(ctx, opts) -} - -type FakeCoreClientSet struct { - PodsFunc func(namespace string) *FakePodInterface -} - -func (f *FakeCoreClientSet) Pods(namespace string) *FakePodInterface { - return f.PodsFunc(namespace) -} - +// (Removed custom fake types; use fake.NewSimpleClientset instead) func TestNICJobList_ExecJobs(t *testing.T) { tmpDir := t.TempDir() dc := &data_collector.DataCollector{ From e124a26e2f8ad210bf17ccddcc04a7c3f87938fd Mon Sep 17 00:00:00 2001 From: Madhu Rajagopal Date: Thu, 4 Sep 2025 16:45:35 +1200 Subject: [PATCH 10/15] Update pkg/jobs/job_test.go Remove redundant mock-logger code. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/jobs/job_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/jobs/job_test.go b/pkg/jobs/job_test.go index c0cfd6c..de1b4e2 100644 --- a/pkg/jobs/job_test.go +++ b/pkg/jobs/job_test.go @@ -13,11 +13,6 @@ import ( "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" ) -// mockLogger implements the minimal Printf interface for testing -// type mockLogger struct{} - -// func (l *mockLogger) Printf(format string, v ...interface{}) {} - // Test successful job execution and file writing func TestJobCollect_Success(t *testing.T) { tmpDir := t.TempDir() From 1922e67753d76d8dc6e6f1f716d421e93325e90f Mon Sep 17 00:00:00 2001 From: Madhu Rajagopal Date: Thu, 4 Sep 2025 17:16:32 +1200 Subject: [PATCH 11/15] Update pkg/jobs/nic_job_list_test.go Remove irrelevant comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/jobs/nic_job_list_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/jobs/nic_job_list_test.go b/pkg/jobs/nic_job_list_test.go index 4ed634d..08a1ff3 100644 --- a/pkg/jobs/nic_job_list_test.go +++ b/pkg/jobs/nic_job_list_test.go @@ -27,7 +27,6 @@ func mockQueryCRD(crd crds.Crd, namespace string, ctx context.Context) ([]byte, return json.Marshal(map[string]string{"kind": crd.Resource}) } -// (Removed custom fake types; use fake.NewSimpleClientset instead) func TestNICJobList_ExecJobs(t *testing.T) { tmpDir := t.TempDir() dc := &data_collector.DataCollector{ From 2c2bd5c447c44b06d93dea46bf66e8856257f83d Mon Sep 17 00:00:00 2001 From: Madhu Rajagopal Date: Thu, 4 Sep 2025 17:31:15 +1200 Subject: [PATCH 12/15] Update pkg/jobs/common_job_list_test.go Taking the address of a slice element from a function call creates a pointer to a temporary value. Store the slice in a variable first, then take the address: jobs := CommonJobList(); podListJob = &jobs[i] Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/jobs/common_job_list_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/jobs/common_job_list_test.go b/pkg/jobs/common_job_list_test.go index b9bb0f4..121fc91 100644 --- a/pkg/jobs/common_job_list_test.go +++ b/pkg/jobs/common_job_list_test.go @@ -125,9 +125,10 @@ func TestCommonJobList_SelectedJobsProduceFiles(t *testing.T) { func TestCommonJobList_PodListJSONKeyPresence(t *testing.T) { dc := setupDataCollector(t) var podListJob *Job - for i, j := range CommonJobList() { + jobs := CommonJobList() + for i, j := range jobs { if j.Name == "pod-list" { - podListJob = &CommonJobList()[i] + podListJob = &jobs[i] break } } From 3170f31449207542a770e7b9ad7155c82e465d38 Mon Sep 17 00:00:00 2001 From: Madhu RAJAGOPAL Date: Mon, 15 Sep 2025 16:34:08 +1200 Subject: [PATCH 13/15] Fix: Address issues from common_job_list tests * Introduce a mock form helm, metrics clients --- pkg/data_collector/data_collector.go | 5 +- pkg/jobs/common_job_list_test.go | 77 ++++++++++++++++++++++------ 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/pkg/data_collector/data_collector.go b/pkg/data_collector/data_collector.go index 047f705..8ec7365 100644 --- a/pkg/data_collector/data_collector.go +++ b/pkg/data_collector/data_collector.go @@ -36,6 +36,7 @@ import ( "github.com/nginxinc/nginx-k8s-supportpkg/pkg/crds" "github.com/nginxinc/nginx-k8s-supportpkg/pkg/version" corev1 "k8s.io/api/core/v1" + apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" crdClient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -55,8 +56,8 @@ type DataCollector struct { LogFile *os.File K8sRestConfig *rest.Config K8sCoreClientSet kubernetes.Interface - K8sCrdClientSet *crdClient.Clientset - K8sMetricsClientSet *metricsClient.Clientset + K8sCrdClientSet apiextensionsclientset.Interface + K8sMetricsClientSet metricsClient.Interface K8sHelmClientSet map[string]helmClient.Client ExcludeDBData bool ExcludeTimeSeriesData bool diff --git a/pkg/jobs/common_job_list_test.go b/pkg/jobs/common_job_list_test.go index 121fc91..3002776 100644 --- a/pkg/jobs/common_job_list_test.go +++ b/pkg/jobs/common_job_list_test.go @@ -10,6 +10,8 @@ import ( "time" "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/release" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -17,7 +19,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + helmclient "github.com/mittwald/go-helm-client" + mockHelmClient "github.com/mittwald/go-helm-client/mock" + "go.uber.org/mock/gomock" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/rest" + metricsfake "k8s.io/metrics/pkg/client/clientset/versioned/fake" ) // helper creates int32 ptr @@ -64,34 +74,67 @@ func setupDataCollector(t *testing.T) *data_collector.DataCollector { } client := fake.NewSimpleClientset(objs...) + // Mock rest.Config + restConfig := &rest.Config{ + Host: "https://mock-k8s-server", + } + + // Create a CRD clientset (using the real clientset, but not actually connecting) + crd := &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcrd.example.com", + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Kind: "TestCRD", + Plural: "testcrds", + Singular: "testcrd", + }, + Scope: apiextensionsv1.NamespaceScoped, + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1", + Served: true, + Storage: true, + }, + }, + }, + } + // crdClient := &apiextensionsclientset.Clientset{} + crdClient := apiextensionsfake.NewSimpleClientset(crd) + metricsClient := metricsfake.NewSimpleClientset() + // helmClient := &FakeHelmClient{} + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + helmClient := mockHelmClient.NewMockClient(ctrl) + if helmClient == nil { + t.Fail() + } + helmClient.EXPECT().GetSettings().Return(&cli.EnvSettings{}).AnyTimes() + var mockedRelease = release.Release{Name: "test", Namespace: "test", Manifest: "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: example-config\n namespace: default\ndata:\n key: value\n"} + helmClient.EXPECT().ListDeployedReleases().Return([]*release.Release{&mockedRelease}, nil).AnyTimes() return &data_collector.DataCollector{ - BaseDir: tmpDir, - Namespaces: []string{"default"}, - Logger: log.New(io.Discard, "", 0), - K8sCoreClientSet: client, + BaseDir: tmpDir, + Namespaces: []string{"default"}, + Logger: log.New(io.Discard, "", 0), + K8sCoreClientSet: client, + K8sCrdClientSet: crdClient, + K8sRestConfig: restConfig, + K8sMetricsClientSet: metricsClient, + K8sHelmClientSet: map[string]helmclient.Client{"default": helmClient}, + // Leave other client sets nil; we will not execute jobs that depend on them in this focused test. } } func TestCommonJobList_SelectedJobsProduceFiles(t *testing.T) { dc := setupDataCollector(t) - - // Jobs we explicitly validate (keep focused; others require additional fake clients) - targetJobs := map[string]struct{}{ - "pod-list": {}, - "service-list": {}, - "deployment-list": {}, - "roles-list": {}, - "configmap-list": {}, - } - jobList := CommonJobList() for _, job := range jobList { - if _, ok := targetJobs[job.Name]; !ok { - continue - } ch := make(chan JobResult, 1) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) From d954b2e4b6a37264d2fb9e72f6307c5194f2476f Mon Sep 17 00:00:00 2001 From: Madhu RAJAGOPAL Date: Tue, 16 Sep 2025 10:07:26 +1200 Subject: [PATCH 14/15] Fix: Implement a mock data_collector * Also update go.mod and go.sum --- go.mod | 30 ++++---- go.sum | 62 +++++++-------- pkg/jobs/common_job_list_test.go | 126 +------------------------------ pkg/mock/mock_data_collector.go | 121 +++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 168 deletions(-) create mode 100644 pkg/mock/mock_data_collector.go diff --git a/go.mod b/go.mod index 3fad585..80239bf 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,11 @@ module github.com/nginxinc/nginx-k8s-supportpkg go 1.24.3 require ( - github.com/mittwald/go-helm-client v0.12.17 + github.com/mittwald/go-helm-client v0.12.18 github.com/spf13/cobra v1.9.1 - k8s.io/client-go v0.33.1 + go.uber.org/mock v0.5.0 + helm.sh/helm/v3 v3.18.4 + k8s.io/client-go v0.33.2 ) require ( @@ -84,17 +86,15 @@ require ( go.opentelemetry.io/otel/metric v1.36.0 // indirect go.opentelemetry.io/otel/sdk v1.36.0 // indirect go.opentelemetry.io/otel/trace v1.36.0 // indirect - golang.org/x/crypto v0.38.0 // indirect - golang.org/x/sync v0.14.0 // indirect - golang.org/x/tools v0.33.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/sync v0.15.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250519155744-55703ea1f237 // indirect google.golang.org/grpc v1.72.1 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect - helm.sh/helm/v3 v3.18.0 // indirect - k8s.io/apiserver v0.33.1 // indirect - k8s.io/cli-runtime v0.33.1 // indirect - k8s.io/component-base v0.33.1 // indirect - k8s.io/kubectl v0.33.1 // indirect + k8s.io/apiserver v0.33.2 // indirect + k8s.io/cli-runtime v0.33.2 // indirect + k8s.io/component-base v0.33.2 // indirect + k8s.io/kubectl v0.33.2 // indirect oras.land/oras-go/v2 v2.6.0 // indirect sigs.k8s.io/kustomize/api v0.19.0 // indirect sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect @@ -122,17 +122,17 @@ require ( golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect - golang.org/x/text v0.25.0 // indirect + golang.org/x/text v0.26.0 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.33.1 - k8s.io/apiextensions-apiserver v0.33.1 - k8s.io/apimachinery v0.33.1 + k8s.io/api v0.33.2 + k8s.io/apiextensions-apiserver v0.33.2 + k8s.io/apimachinery v0.33.2 k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect - k8s.io/metrics v0.33.1 + k8s.io/metrics v0.33.2 k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect diff --git a/go.sum b/go.sum index 4bd68de..669833e 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,8 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mittwald/go-helm-client v0.12.17 h1:PncoE1u3fXuHWLineNDQ4hI5J4uVbMW3JWrtdBR86TI= -github.com/mittwald/go-helm-client v0.12.17/go.mod h1:GQxuPspUcMsxWWDtYzjRdxOAjh3LKADIfgqtUf9mjHk= +github.com/mittwald/go-helm-client v0.12.18 h1:i9cJNv/YC3ZPKUKVNYTlrOO7ZO6YFKE/ak3J5TeYHPU= +github.com/mittwald/go-helm-client v0.12.18/go.mod h1:dLl5NkdKCvwKvLIdZzg4MDbxhSKmuimdmM3WpsAzS0I= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= @@ -330,15 +330,17 @@ go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9f go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -350,8 +352,8 @@ golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKl golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -364,8 +366,8 @@ golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -399,30 +401,30 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -helm.sh/helm/v3 v3.18.0 h1:ItOAm3Quo0dus3NUHjs+lluqWWEIO7xrSW+zKWCrvlw= -helm.sh/helm/v3 v3.18.0/go.mod h1:43QHS1W97RcoFJRk36ZBhHdTfykqBlJdsWp3yhzdq8w= -k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= -k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= -k8s.io/apiextensions-apiserver v0.33.1 h1:N7ccbSlRN6I2QBcXevB73PixX2dQNIW0ZRuguEE91zI= -k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA= -k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= -k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.1 h1:yLgLUPDVC6tHbNcw5uE9mo1T6ELhJj7B0geifra3Qdo= -k8s.io/apiserver v0.33.1/go.mod h1:VMbE4ArWYLO01omz+k8hFjAdYfc3GVAYPrhP2tTKccs= -k8s.io/cli-runtime v0.33.1 h1:TvpjEtF71ViFmPeYMj1baZMJR4iWUEplklsUQ7D3quA= -k8s.io/cli-runtime v0.33.1/go.mod h1:9dz5Q4Uh8io4OWCLiEf/217DXwqNgiTS/IOuza99VZE= -k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= -k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= -k8s.io/component-base v0.33.1 h1:EoJ0xA+wr77T+G8p6T3l4efT2oNwbqBVKR71E0tBIaI= -k8s.io/component-base v0.33.1/go.mod h1:guT/w/6piyPfTgq7gfvgetyXMIh10zuXA6cRRm3rDuY= +helm.sh/helm/v3 v3.18.4 h1:pNhnHM3nAmDrxz6/UC+hfjDY4yeDATQCka2/87hkZXQ= +helm.sh/helm/v3 v3.18.4/go.mod h1:WVnwKARAw01iEdjpEkP7Ii1tT1pTPYfM1HsakFKM3LI= +k8s.io/api v0.33.2 h1:YgwIS5jKfA+BZg//OQhkJNIfie/kmRsO0BmNaVSimvY= +k8s.io/api v0.33.2/go.mod h1:fhrbphQJSM2cXzCWgqU29xLDuks4mu7ti9vveEnpSXs= +k8s.io/apiextensions-apiserver v0.33.2 h1:6gnkIbngnaUflR3XwE1mCefN3YS8yTD631JXQhsU6M8= +k8s.io/apiextensions-apiserver v0.33.2/go.mod h1:IvVanieYsEHJImTKXGP6XCOjTwv2LUMos0YWc9O+QP8= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.2 h1:KGTRbxn2wJagJowo29kKBp4TchpO1DRO3g+dB/KOJN4= +k8s.io/apiserver v0.33.2/go.mod h1:9qday04wEAMLPWWo9AwqCZSiIn3OYSZacDyu/AcoM/M= +k8s.io/cli-runtime v0.33.2 h1:koNYQKSDdq5AExa/RDudXMhhtFasEg48KLS2KSAU74Y= +k8s.io/cli-runtime v0.33.2/go.mod h1:gnhsAWpovqf1Zj5YRRBBU7PFsRc6NkEkwYNQE+mXL88= +k8s.io/client-go v0.33.2 h1:z8CIcc0P581x/J1ZYf4CNzRKxRvQAwoAolYPbtQes+E= +k8s.io/client-go v0.33.2/go.mod h1:9mCgT4wROvL948w6f6ArJNb7yQd7QsvqavDeZHvNmHo= +k8s.io/component-base v0.33.2 h1:sCCsn9s/dG3ZrQTX/Us0/Sx2R0G5kwa0wbZFYoVp/+0= +k8s.io/component-base v0.33.2/go.mod h1:/41uw9wKzuelhN+u+/C59ixxf4tYQKW7p32ddkYNe2k= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/kubectl v0.33.1 h1:OJUXa6FV5bap6iRy345ezEjU9dTLxqv1zFTVqmeHb6A= -k8s.io/kubectl v0.33.1/go.mod h1:Z07pGqXoP4NgITlPRrnmiM3qnoo1QrK1zjw85Aiz8J0= -k8s.io/metrics v0.33.1 h1:Ypd5ITCf+fM+LDNFk7hESXTc3vh02CQYGiwRoVRaGsM= -k8s.io/metrics v0.33.1/go.mod h1:wK8cFTK5ykBdhL0Wy4RZwLH28XM7j/Klc+NQrMRWVxg= +k8s.io/kubectl v0.33.2 h1:7XKZ6DYCklu5MZQzJe+CkCjoGZwD1wWl7t/FxzhMz7Y= +k8s.io/kubectl v0.33.2/go.mod h1:8rC67FB8tVTYraovAGNi/idWIK90z2CHFNMmGJZJ3KI= +k8s.io/metrics v0.33.2 h1:gNCBmtnUMDMCRg9Ly5ehxP3OdKISMsOnh1vzk01iCgE= +k8s.io/metrics v0.33.2/go.mod h1:yxoAosKGRsZisv3BGekC5W6T1J8XSV+PoUEevACRv7c= k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg= k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= diff --git a/pkg/jobs/common_job_list_test.go b/pkg/jobs/common_job_list_test.go index 3002776..4e10ac2 100644 --- a/pkg/jobs/common_job_list_test.go +++ b/pkg/jobs/common_job_list_test.go @@ -2,136 +2,16 @@ package jobs import ( "context" - "io" - "log" "path/filepath" "strings" "testing" "time" - "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/release" - - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - helmclient "github.com/mittwald/go-helm-client" - mockHelmClient "github.com/mittwald/go-helm-client/mock" - "go.uber.org/mock/gomock" - - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" - "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/rest" - metricsfake "k8s.io/metrics/pkg/client/clientset/versioned/fake" + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/mock" ) -// helper creates int32 ptr -func i32(v int32) *int32 { return &v } - -func setupDataCollector(t *testing.T) *data_collector.DataCollector { - t.Helper() - - tmpDir := t.TempDir() - - // Seed fake objects (namespace default implied by metadata) - objs := []runtime.Object{ - &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{Name: "pod-1", Namespace: "default"}, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{Name: "c1", Image: "nginx:latest"}}, - }, - }, - &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{Name: "svc-1", Namespace: "default"}, - Spec: corev1.ServiceSpec{Selector: map[string]string{"app": "demo"}}, - }, - &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{Name: "dep-1", Namespace: "default"}, - Spec: appsv1.DeploymentSpec{ - Replicas: i32(1), - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "demo"}}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "demo"}}, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{Name: "dep-c1", Image: "nginx:latest"}}, - }, - }, - }, - }, - &rbacv1.Role{ - ObjectMeta: metav1.ObjectMeta{Name: "role-1", Namespace: "default"}, - Rules: []rbacv1.PolicyRule{{APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"get"}}}, - }, - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Name: "cm-1", Namespace: "default"}, - Data: map[string]string{"k": "v"}, - }, - } - - client := fake.NewSimpleClientset(objs...) - // Mock rest.Config - restConfig := &rest.Config{ - Host: "https://mock-k8s-server", - } - - // Create a CRD clientset (using the real clientset, but not actually connecting) - crd := &apiextensionsv1.CustomResourceDefinition{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcrd.example.com", - }, - Spec: apiextensionsv1.CustomResourceDefinitionSpec{ - Group: "example.com", - Names: apiextensionsv1.CustomResourceDefinitionNames{ - Kind: "TestCRD", - Plural: "testcrds", - Singular: "testcrd", - }, - Scope: apiextensionsv1.NamespaceScoped, - Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ - { - Name: "v1", - Served: true, - Storage: true, - }, - }, - }, - } - // crdClient := &apiextensionsclientset.Clientset{} - crdClient := apiextensionsfake.NewSimpleClientset(crd) - metricsClient := metricsfake.NewSimpleClientset() - // helmClient := &FakeHelmClient{} - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - helmClient := mockHelmClient.NewMockClient(ctrl) - if helmClient == nil { - t.Fail() - } - helmClient.EXPECT().GetSettings().Return(&cli.EnvSettings{}).AnyTimes() - var mockedRelease = release.Release{Name: "test", Namespace: "test", Manifest: "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: example-config\n namespace: default\ndata:\n key: value\n"} - helmClient.EXPECT().ListDeployedReleases().Return([]*release.Release{&mockedRelease}, nil).AnyTimes() - - return &data_collector.DataCollector{ - BaseDir: tmpDir, - Namespaces: []string{"default"}, - Logger: log.New(io.Discard, "", 0), - K8sCoreClientSet: client, - K8sCrdClientSet: crdClient, - K8sRestConfig: restConfig, - K8sMetricsClientSet: metricsClient, - K8sHelmClientSet: map[string]helmclient.Client{"default": helmClient}, - - // Leave other client sets nil; we will not execute jobs that depend on them in this focused test. - } -} - func TestCommonJobList_SelectedJobsProduceFiles(t *testing.T) { - dc := setupDataCollector(t) + dc := mock.SetupMockDataCollector(t) jobList := CommonJobList() for _, job := range jobList { @@ -166,7 +46,7 @@ func TestCommonJobList_SelectedJobsProduceFiles(t *testing.T) { } func TestCommonJobList_PodListJSONKeyPresence(t *testing.T) { - dc := setupDataCollector(t) + dc := mock.SetupMockDataCollector(t) var podListJob *Job jobs := CommonJobList() for i, j := range jobs { diff --git a/pkg/mock/mock_data_collector.go b/pkg/mock/mock_data_collector.go new file mode 100644 index 0000000..b1b0f3f --- /dev/null +++ b/pkg/mock/mock_data_collector.go @@ -0,0 +1,121 @@ +package mock + +import ( + "io" + "log" + "testing" + + helmclient "github.com/mittwald/go-helm-client" + mockHelmClient "github.com/mittwald/go-helm-client/mock" + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + "go.uber.org/mock/gomock" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/release" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsfake "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/rest" + metricsfake "k8s.io/metrics/pkg/client/clientset/versioned/fake" +) + +// helper creates int32 ptr +func i32(v int32) *int32 { return &v } + +func SetupMockDataCollector(t *testing.T) *data_collector.DataCollector { + t.Helper() + + tmpDir := t.TempDir() + + // Seed fake objects (namespace default implied by metadata) + objs := []runtime.Object{ + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "pod-1", Namespace: "default"}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "c1", Image: "nginx:latest"}}, + }, + }, + &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{Name: "svc-1", Namespace: "default"}, + Spec: corev1.ServiceSpec{Selector: map[string]string{"app": "demo"}}, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{Name: "dep-1", Namespace: "default"}, + Spec: appsv1.DeploymentSpec{ + Replicas: i32(1), + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "demo"}}, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "demo"}}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "dep-c1", Image: "nginx:latest"}}, + }, + }, + }, + }, + &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{Name: "role-1", Namespace: "default"}, + Rules: []rbacv1.PolicyRule{{APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"get"}}}, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Name: "cm-1", Namespace: "default"}, + Data: map[string]string{"k": "v"}, + }, + } + + client := fake.NewSimpleClientset(objs...) + // Mock rest.Config + restConfig := &rest.Config{ + Host: "https://mock-k8s-server", + } + + // Create a CRD clientset (using the real clientset, but not actually connecting) + crd := &apiextensionsv1.CustomResourceDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcrd.example.com", + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "example.com", + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Kind: "TestCRD", + Plural: "testcrds", + Singular: "testcrd", + }, + Scope: apiextensionsv1.NamespaceScoped, + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1", + Served: true, + Storage: true, + }, + }, + }, + } + + crdClient := apiextensionsfake.NewSimpleClientset(crd) + metricsClient := metricsfake.NewSimpleClientset() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + helmClient := mockHelmClient.NewMockClient(ctrl) + if helmClient == nil { + t.Fail() + } + helmClient.EXPECT().GetSettings().Return(&cli.EnvSettings{}).AnyTimes() + var mockedRelease = release.Release{Name: "test", Namespace: "test", Manifest: "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: example-config\n namespace: default\ndata:\n key: value\n"} + helmClient.EXPECT().ListDeployedReleases().Return([]*release.Release{&mockedRelease}, nil).AnyTimes() + + return &data_collector.DataCollector{ + BaseDir: tmpDir, + Namespaces: []string{"default"}, + Logger: log.New(io.Discard, "", 0), + K8sCoreClientSet: client, + K8sCrdClientSet: crdClient, + K8sRestConfig: restConfig, + K8sMetricsClientSet: metricsClient, + K8sHelmClientSet: map[string]helmclient.Client{"default": helmClient}, + } +} From 5e2bd8784160154381797ac9e9078770e2100226 Mon Sep 17 00:00:00 2001 From: Madhu RAJAGOPAL Date: Tue, 16 Sep 2025 11:36:56 +1200 Subject: [PATCH 15/15] Fix: Use the mock data_collector in various tests --- pkg/jobs/job_test.go | 27 +++++-------------- pkg/jobs/ngf_job_list_test.go | 30 ++++++++------------- pkg/jobs/ngx_job_list_test.go | 21 +++++++-------- pkg/jobs/nic_job_list_test.go | 25 ++++++------------ pkg/jobs/nim_job_list_test.go | 50 +++++++++++++++-------------------- 5 files changed, 57 insertions(+), 96 deletions(-) diff --git a/pkg/jobs/job_test.go b/pkg/jobs/job_test.go index de1b4e2..b1aeda7 100644 --- a/pkg/jobs/job_test.go +++ b/pkg/jobs/job_test.go @@ -3,24 +3,18 @@ package jobs import ( "context" "errors" - "io" - "log" "os" "path/filepath" "testing" "time" "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/mock" ) // Test successful job execution and file writing func TestJobCollect_Success(t *testing.T) { - tmpDir := t.TempDir() - dc := &data_collector.DataCollector{ - BaseDir: tmpDir, - Logger: log.New(io.Discard, "", 0), - } - + dc := mock.SetupMockDataCollector(t) job := Job{ Name: "test-job", Timeout: time.Second, @@ -40,7 +34,7 @@ func TestJobCollect_Success(t *testing.T) { t.Fatalf("expected not skipped") } // Check file was written - content, err := os.ReadFile(filepath.Join(tmpDir, "output.txt")) + content, err := os.ReadFile(filepath.Join(dc.BaseDir, "output.txt")) if err != nil { t.Fatalf("file not written: %v", err) } @@ -51,10 +45,7 @@ func TestJobCollect_Success(t *testing.T) { // Test job skipped scenario func TestJobCollect_Skipped(t *testing.T) { - dc := &data_collector.DataCollector{ - BaseDir: t.TempDir(), - Logger: log.New(io.Discard, "", 0), - } + dc := mock.SetupMockDataCollector(t) job := Job{ Name: "skip-job", Timeout: time.Second, @@ -73,10 +64,7 @@ func TestJobCollect_Skipped(t *testing.T) { // Test job error scenario func TestJobCollect_Error(t *testing.T) { - dc := &data_collector.DataCollector{ - BaseDir: t.TempDir(), - Logger: log.New(io.Discard, "", 0), - } + dc := mock.SetupMockDataCollector(t) job := Job{ Name: "error-job", Timeout: time.Second, @@ -95,10 +83,7 @@ func TestJobCollect_Error(t *testing.T) { // Test job timeout scenario func TestJobCollect_Timeout(t *testing.T) { - dc := &data_collector.DataCollector{ - BaseDir: t.TempDir(), - Logger: log.New(io.Discard, "", 0), - } + dc := mock.SetupMockDataCollector(t) job := Job{ Name: "timeout-job", Timeout: time.Millisecond * 10, diff --git a/pkg/jobs/ngf_job_list_test.go b/pkg/jobs/ngf_job_list_test.go index f2059fa..735604b 100644 --- a/pkg/jobs/ngf_job_list_test.go +++ b/pkg/jobs/ngf_job_list_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/mock" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" @@ -38,7 +38,6 @@ func TestNGFJobList(t *testing.T) { } func TestNGFJobExecNginxGatewayVersion(t *testing.T) { - tmpDir := t.TempDir() client := fake.NewSimpleClientset(&corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "nginx-gateway-test-pod", @@ -51,14 +50,10 @@ func TestNGFJobExecNginxGatewayVersion(t *testing.T) { }, }) - dc := &data_collector.DataCollector{ - BaseDir: tmpDir, - Namespaces: []string{"default"}, - K8sCoreClientSet: client, - Logger: log.New(io.Discard, "", 0), - PodExecutor: func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { - return []byte("gateway version output"), nil - }, + dc := mock.SetupMockDataCollector(t) + dc.K8sCoreClientSet = client + dc.PodExecutor = func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { + return []byte("gateway version output"), nil } jobs := NGFJobList() @@ -97,7 +92,6 @@ func TestNGFJobExecNginxGatewayVersion(t *testing.T) { } func TestNGFJobExecNginxT(t *testing.T) { - tmpDir := t.TempDir() client := fake.NewSimpleClientset(&corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "nginx-gateway-test-pod", @@ -110,15 +104,13 @@ func TestNGFJobExecNginxT(t *testing.T) { }, }) - dc := &data_collector.DataCollector{ - BaseDir: tmpDir, - Namespaces: []string{"default"}, - K8sCoreClientSet: client, - Logger: log.New(io.Discard, "", 0), - PodExecutor: func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { - return []byte("nginx -t output"), nil - }, + dc := mock.SetupMockDataCollector(t) + dc.Namespaces = []string{"default"} + dc.K8sCoreClientSet = client + dc.PodExecutor = func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { + return []byte("nginx -t output"), nil } + dc.Logger = log.New(io.Discard, "", 0) jobs := NGFJobList() var tJob Job diff --git a/pkg/jobs/ngx_job_list_test.go b/pkg/jobs/ngx_job_list_test.go index 46c88b9..8f85f64 100644 --- a/pkg/jobs/ngx_job_list_test.go +++ b/pkg/jobs/ngx_job_list_test.go @@ -2,26 +2,25 @@ package jobs import ( "context" - "io" - "log" "path/filepath" "strings" "testing" "time" - "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/mock" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" ) func TestNGXJobList_ExecNginxT(t *testing.T) { - tmpDir := t.TempDir() - dc := &data_collector.DataCollector{ - BaseDir: tmpDir, - Logger: log.New(io.Discard, "", 0), - Namespaces: []string{"default"}, - } + // dc := &data_collector.DataCollector{ + // BaseDir: tmpDir, + // Logger: log.New(io.Discard, "", 0), + // Namespaces: []string{"default"}, + // } + + dc := mock.SetupMockDataCollector(t) // Create a fake pod named "nginx-123" in the "default" namespace pod := &corev1.Pod{ @@ -62,8 +61,8 @@ func TestNGXJobList_ExecNginxT(t *testing.T) { if string(content) != "nginx -T output" { t.Errorf("unexpected file content: %s", string(content)) } - if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(tmpDir)) { - t.Errorf("file path %s does not start with tmpDir %s", file, tmpDir) + if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(dc.BaseDir)) { + t.Errorf("file path %s does not start with tmpDir %s", file, dc.BaseDir) } found = true } diff --git a/pkg/jobs/nic_job_list_test.go b/pkg/jobs/nic_job_list_test.go index 08a1ff3..766a002 100644 --- a/pkg/jobs/nic_job_list_test.go +++ b/pkg/jobs/nic_job_list_test.go @@ -3,15 +3,13 @@ package jobs import ( "context" "encoding/json" - "io" - "log" "path/filepath" "strings" "testing" "time" "github.com/nginxinc/nginx-k8s-supportpkg/pkg/crds" - "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/mock" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" @@ -28,12 +26,9 @@ func mockQueryCRD(crd crds.Crd, namespace string, ctx context.Context) ([]byte, } func TestNICJobList_ExecJobs(t *testing.T) { - tmpDir := t.TempDir() - dc := &data_collector.DataCollector{ - BaseDir: tmpDir, - Logger: log.New(io.Discard, "", 0), - Namespaces: []string{"test-ns"}, - } + dc := mock.SetupMockDataCollector(t) + dc.Namespaces = []string{"test-ns"} + // Mock PodExecutor and QueryCRD dc.PodExecutor = mockPodExecutor dc.QueryCRD = mockQueryCRD @@ -58,7 +53,7 @@ func TestNICJobList_ExecJobs(t *testing.T) { t.Errorf("Job %s returned error: %v", job.Name, result.Error) } for file, content := range result.Files { - if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(tmpDir)) { + if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(dc.BaseDir)) { t.Errorf("File path %s does not start with tmpDir", file) } if len(content) == 0 { @@ -72,12 +67,8 @@ func TestNICJobList_ExecJobs(t *testing.T) { } func TestNICJobList_CRDObjects(t *testing.T) { - tmpDir := t.TempDir() - dc := &data_collector.DataCollector{ - BaseDir: tmpDir, - Logger: log.New(io.Discard, "", 0), - Namespaces: []string{"test-ns"}, - } + dc := mock.SetupMockDataCollector(t) + dc.Namespaces = []string{"test-ns"} dc.QueryCRD = mockQueryCRD jobList := NICJobList() @@ -93,7 +84,7 @@ func TestNICJobList_CRDObjects(t *testing.T) { t.Errorf("CRD job returned error: %v", result.Error) } for file, content := range result.Files { - if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(tmpDir)) { + if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(dc.BaseDir)) { t.Errorf("File path %s does not start with tmpDir", file) } var out map[string]interface{} diff --git a/pkg/jobs/nim_job_list_test.go b/pkg/jobs/nim_job_list_test.go index 6cf32c9..e4eed6e 100644 --- a/pkg/jobs/nim_job_list_test.go +++ b/pkg/jobs/nim_job_list_test.go @@ -2,14 +2,12 @@ package jobs import ( "context" - "io" - "log" "path/filepath" "strings" "testing" "time" - "github.com/nginxinc/nginx-k8s-supportpkg/pkg/data_collector" + "github.com/nginxinc/nginx-k8s-supportpkg/pkg/mock" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -17,12 +15,13 @@ import ( ) func TestNIMJobList_ExecJobs(t *testing.T) { - tmpDir := t.TempDir() - dc := &data_collector.DataCollector{ - BaseDir: tmpDir, - Logger: log.New(io.Discard, "", 0), - Namespaces: []string{"default"}, - } + // dc := &data_collector.DataCollector{ + // BaseDir: tmpDir, + // Logger: log.New(io.Discard, "", 0), + // Namespaces: []string{"default"}, + // } + dc := mock.SetupMockDataCollector(t) + dc.Namespaces = []string{"default"} // Create fake pods for each job type pods := []*corev1.Pod{ @@ -94,8 +93,8 @@ func TestNIMJobList_ExecJobs(t *testing.T) { t.Errorf("Job %s returned error: %v", job.Name, result.Error) } for file, content := range result.Files { - if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(tmpDir)) { - t.Errorf("File path %s does not start with tmpDir %s", file, tmpDir) + if !strings.HasPrefix(filepath.ToSlash(file), filepath.ToSlash(dc.BaseDir)) { + t.Errorf("File path %s does not start with tmpDir %s", file, dc.BaseDir) } if len(content) == 0 { t.Errorf("File %s has empty content", file) @@ -108,25 +107,20 @@ func TestNIMJobList_ExecJobs(t *testing.T) { } func TestNIMJobList_ExcludeFlags(t *testing.T) { - tmpDir := t.TempDir() - dc := &data_collector.DataCollector{ - BaseDir: tmpDir, - Logger: log.New(io.Discard, "", 0), - Namespaces: []string{"default"}, - K8sCoreClientSet: fake.NewSimpleClientset(&corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "clickhouse-456", - Namespace: "default", - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{{Name: "clickhouse-server"}}, - }, - }), - PodExecutor: func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { - return []byte("output"), nil + dc := mock.SetupMockDataCollector(t) + dc.Namespaces = []string{"default"} + dc.K8sCoreClientSet = fake.NewSimpleClientset(&corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "clickhouse-456", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Name: "clickhouse-server"}}, }, + }) + dc.PodExecutor = func(namespace, pod, container string, command []string, ctx context.Context) ([]byte, error) { + return []byte("output"), nil } - // Test ExcludeTimeSeriesData for exec-clickhouse-data dc.ExcludeTimeSeriesData = true for _, job := range NIMJobList() {