Przeglądaj źródła

Tests for Kubemodel registration

Signed-off-by: Sean Holcomb <seanholcomb@gmail.com>
Sean Holcomb 4 tygodni temu
rodzic
commit
d7e0ebae71

+ 131 - 0
core/pkg/model/kubemodel/cluster_test.go

@@ -0,0 +1,131 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateCluster(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name    string
+		cluster *Cluster
+		wantErr string
+	}{
+		{
+			name:    "empty UID",
+			cluster: &Cluster{Name: "my-cluster", Start: start, End: end},
+			wantErr: "UID is missing for Cluster with name 'my-cluster'",
+		},
+		{
+			name:    "outside window",
+			cluster: &Cluster{UID: "cluster-uid", Name: "my-cluster", Start: start.Add(-time.Hour), End: end},
+			wantErr: checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name:    "valid",
+			cluster: &Cluster{UID: "cluster-uid", Name: "my-cluster", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.cluster.ValidateCluster(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterCluster(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newCluster := func(uid, name string) *Cluster {
+		return &Cluster{UID: uid, Name: name, Start: start, End: end}
+	}
+
+	tests := []struct {
+		name    string
+		setup   func(*KubeModelSet)
+		cluster *Cluster
+		wantErr string
+		want    *KubeModelSet
+	}{
+		{
+			name:    "validation failure",
+			cluster: &Cluster{UID: "", Name: "my-cluster", Start: start, End: end},
+			wantErr: "RegisterCluster: invalid cluster: UID is missing for Cluster with name 'my-cluster'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterCluster: invalid cluster: UID is missing for Cluster with name 'my-cluster'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:    "registers cluster",
+			cluster: newCluster("cluster-uid", "my-cluster"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Cluster = newCluster("cluster-uid", "my-cluster")
+				return kms
+			}(),
+		},
+		{
+			name: "same UID is a no-op",
+			setup: func(kms *KubeModelSet) {
+				kms.RegisterCluster(newCluster("cluster-uid", "original"))
+			},
+			cluster: newCluster("cluster-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Cluster = newCluster("cluster-uid", "original")
+				return kms
+			}(),
+		},
+		{
+			name: "different UID emits warning and keeps original",
+			setup: func(kms *KubeModelSet) {
+				kms.RegisterCluster(newCluster("cluster-uid", "original"))
+			},
+			cluster: newCluster("another-uid", "another"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Cluster = newCluster("cluster-uid", "original")
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelWarning, Message: "RegisterCluster(another-uid): attempting to change cluster UID from cluster-uid to another-uid"},
+				}
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterCluster(tt.cluster)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 123 - 0
core/pkg/model/kubemodel/container_test.go

@@ -0,0 +1,123 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateContainer(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name      string
+		container *Container
+		wantErr   string
+	}{
+		{
+			name:      "empty PodUID",
+			container: &Container{Name: "my-container", Start: start, End: end},
+			wantErr:   "PodUID is missing for Container with name 'my-container'",
+		},
+		{
+			name:      "empty Name",
+			container: &Container{PodUID: "pod-uid", Start: start, End: end},
+			wantErr:   "Name is missing for Container on pod 'pod-uid'",
+		},
+		{
+			name:      "outside window",
+			container: &Container{PodUID: "pod-uid", Name: "my-container", Start: start.Add(-time.Hour), End: end},
+			wantErr:   checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name:      "valid",
+			container: &Container{PodUID: "pod-uid", Name: "my-container", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.container.ValidateContainer(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterContainer(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newContainer := func(podUID, name string) *Container {
+		return &Container{PodUID: podUID, Name: name, Start: start, End: end}
+	}
+
+	tests := []struct {
+		name      string
+		setup     func(*KubeModelSet)
+		container *Container
+		wantErr   string
+		want      *KubeModelSet
+	}{
+		{
+			name:      "validation failure",
+			container: &Container{PodUID: "", Name: "my-container", Start: start, End: end},
+			wantErr:   "RegisterContainer: invalid container: PodUID is missing for Container with name 'my-container'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterContainer: invalid container: PodUID is missing for Container with name 'my-container'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:      "registers container",
+			container: newContainer("pod-uid", "my-container"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Containers["pod-uid/my-container"] = newContainer("pod-uid", "my-container")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				kms.RegisterContainer(newContainer("pod-uid", "original"))
+			},
+			container: newContainer("pod-uid", "original"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Containers["pod-uid/original"] = newContainer("pod-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterContainer(tt.container)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 148 - 0
core/pkg/model/kubemodel/cronjob_test.go

@@ -0,0 +1,148 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateCronJob(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name    string
+		cronJob *CronJob
+		wantErr string
+	}{
+		{
+			name:    "empty UID",
+			cronJob: &CronJob{Name: "my-cj", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "UID is missing for CronJob with name 'my-cj'",
+		},
+		{
+			name:    "empty Name",
+			cronJob: &CronJob{UID: "cj-uid", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "Name is missing for CronJob 'cj-uid'",
+		},
+		{
+			name:    "empty NamespaceUID",
+			cronJob: &CronJob{UID: "cj-uid", Name: "my-cj", Start: start, End: end},
+			wantErr: "NamespaceUID is missing for CronJob 'cj-uid'",
+		},
+		{
+			name:    "outside window",
+			cronJob: &CronJob{UID: "cj-uid", Name: "my-cj", NamespaceUID: "ns-uid", Start: start.Add(-time.Hour), End: end},
+			wantErr: checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name:    "valid",
+			cronJob: &CronJob{UID: "cj-uid", Name: "my-cj", NamespaceUID: "ns-uid", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.cronJob.ValidateCronJob(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterCronJob(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newCronJob := func(uid, name string) *CronJob {
+		return &CronJob{UID: uid, Name: name, NamespaceUID: "ns-uid", Start: start, End: end}
+	}
+	withCluster := func(kms *KubeModelSet) {
+		kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
+	}
+
+	tests := []struct {
+		name    string
+		setup   func(*KubeModelSet)
+		cronJob *CronJob
+		wantErr string
+		want    *KubeModelSet
+	}{
+		{
+			name:    "validation failure",
+			cronJob: &CronJob{UID: "", Name: "my-cj", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "RegisterCronJob: invalid cronjob: UID is missing for CronJob with name 'my-cj'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterCronJob: invalid cronjob: UID is missing for CronJob with name 'my-cj'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:    "warns when cluster is nil",
+			cronJob: newCronJob("cj-uid", "my-cj"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.CronJobs["cj-uid"] = newCronJob("cj-uid", "my-cj")
+				kms.Metadata.ObjectCount = 1
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelWarning, Message: "RegisterCronJob: Cluster is nil"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:    "registers cronjob with cluster",
+			setup:   withCluster,
+			cronJob: newCronJob("cj-uid", "my-cj"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.CronJobs["cj-uid"] = newCronJob("cj-uid", "my-cj")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				withCluster(kms)
+				kms.RegisterCronJob(newCronJob("cj-uid", "original"))
+			},
+			cronJob: newCronJob("cj-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.CronJobs["cj-uid"] = newCronJob("cj-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterCronJob(tt.cronJob)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 148 - 0
core/pkg/model/kubemodel/daemonset_test.go

@@ -0,0 +1,148 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateDaemonSet(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name      string
+		daemonSet *DaemonSet
+		wantErr   string
+	}{
+		{
+			name:      "empty UID",
+			daemonSet: &DaemonSet{Name: "my-ds", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:   "UID is missing for DaemonSet with name 'my-ds'",
+		},
+		{
+			name:      "empty Name",
+			daemonSet: &DaemonSet{UID: "ds-uid", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:   "Name is missing for DaemonSet 'ds-uid'",
+		},
+		{
+			name:      "empty NamespaceUID",
+			daemonSet: &DaemonSet{UID: "ds-uid", Name: "my-ds", Start: start, End: end},
+			wantErr:   "NamespaceUID is missing for DaemonSet 'ds-uid'",
+		},
+		{
+			name:      "outside window",
+			daemonSet: &DaemonSet{UID: "ds-uid", Name: "my-ds", NamespaceUID: "ns-uid", Start: start.Add(-time.Hour), End: end},
+			wantErr:   checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name:      "valid",
+			daemonSet: &DaemonSet{UID: "ds-uid", Name: "my-ds", NamespaceUID: "ns-uid", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.daemonSet.ValidateDaemonSet(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterDaemonSet(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newDaemonSet := func(uid, name string) *DaemonSet {
+		return &DaemonSet{UID: uid, Name: name, NamespaceUID: "ns-uid", Start: start, End: end}
+	}
+	withCluster := func(kms *KubeModelSet) {
+		kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
+	}
+
+	tests := []struct {
+		name      string
+		setup     func(*KubeModelSet)
+		daemonSet *DaemonSet
+		wantErr   string
+		want      *KubeModelSet
+	}{
+		{
+			name:      "validation failure",
+			daemonSet: &DaemonSet{UID: "", Name: "my-ds", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:   "RegisterDaemonSet: invalid daemonset: UID is missing for DaemonSet with name 'my-ds'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterDaemonSet: invalid daemonset: UID is missing for DaemonSet with name 'my-ds'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:      "warns when cluster is nil",
+			daemonSet: newDaemonSet("ds-uid", "my-ds"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.DaemonSets["ds-uid"] = newDaemonSet("ds-uid", "my-ds")
+				kms.Metadata.ObjectCount = 1
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelWarning, Message: "RegisterDaemonSet: Cluster is nil"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:      "registers daemonset with cluster",
+			setup:     withCluster,
+			daemonSet: newDaemonSet("ds-uid", "my-ds"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.DaemonSets["ds-uid"] = newDaemonSet("ds-uid", "my-ds")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				withCluster(kms)
+				kms.RegisterDaemonSet(newDaemonSet("ds-uid", "original"))
+			},
+			daemonSet: newDaemonSet("ds-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.DaemonSets["ds-uid"] = newDaemonSet("ds-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterDaemonSet(tt.daemonSet)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 138 - 0
core/pkg/model/kubemodel/dcgm_test.go

@@ -0,0 +1,138 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateDCGMDevice(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name   string
+		device *DCGMDevice
+		wantErr string
+	}{
+		{
+			name:    "empty UUID",
+			device:  &DCGMDevice{Device: "GPU-0", Start: start, End: end},
+			wantErr: "UUID is missing for DCGMDevice with device 'GPU-0'",
+		},
+		{
+			name:    "outside window",
+			device:  &DCGMDevice{UUID: "gpu-uuid", Device: "GPU-0", Start: start.Add(-time.Hour), End: end},
+			wantErr: checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name:   "valid",
+			device: &DCGMDevice{UUID: "gpu-uuid", Device: "GPU-0", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.device.ValidateDCGMDevice(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterDCGMDevice(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newDevice := func(uuid, device string) *DCGMDevice {
+		return &DCGMDevice{UUID: uuid, Device: device, Start: start, End: end}
+	}
+	withCluster := func(kms *KubeModelSet) {
+		kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
+	}
+
+	tests := []struct {
+		name    string
+		setup   func(*KubeModelSet)
+		device  *DCGMDevice
+		wantErr string
+		want    *KubeModelSet
+	}{
+		{
+			name:    "validation failure",
+			device:  &DCGMDevice{UUID: "", Device: "GPU-0", Start: start, End: end},
+			wantErr: "RegisterDCGMDevice: invalid dcgm device: UUID is missing for DCGMDevice with device 'GPU-0'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterDCGMDevice: invalid dcgm device: UUID is missing for DCGMDevice with device 'GPU-0'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:   "warns when cluster is nil",
+			device: newDevice("gpu-uuid", "GPU-0"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.DCGMDevices["gpu-uuid"] = newDevice("gpu-uuid", "GPU-0")
+				kms.Metadata.ObjectCount = 1
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelWarning, Message: "RegisterDCGMDevice: Cluster is nil"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:   "registers device with cluster",
+			setup:  withCluster,
+			device: newDevice("gpu-uuid", "GPU-0"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.DCGMDevices["gpu-uuid"] = newDevice("gpu-uuid", "GPU-0")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				withCluster(kms)
+				kms.RegisterDCGMDevice(newDevice("gpu-uuid", "GPU-0"))
+			},
+			device: newDevice("gpu-uuid", "GPU-1"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.DCGMDevices["gpu-uuid"] = newDevice("gpu-uuid", "GPU-0")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterDCGMDevice(tt.device)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 148 - 0
core/pkg/model/kubemodel/deployment_test.go

@@ -0,0 +1,148 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateDeployment(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name       string
+		deployment *Deployment
+		wantErr    string
+	}{
+		{
+			name:       "empty UID",
+			deployment: &Deployment{Name: "my-deployment", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:    "UID is missing for Deployment with name 'my-deployment'",
+		},
+		{
+			name:       "empty Name",
+			deployment: &Deployment{UID: "dep-uid", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:    "Name is missing for Deployment 'dep-uid'",
+		},
+		{
+			name:       "empty NamespaceUID",
+			deployment: &Deployment{UID: "dep-uid", Name: "my-deployment", Start: start, End: end},
+			wantErr:    "NamespaceUID is missing for Deployment 'dep-uid'",
+		},
+		{
+			name:       "outside window",
+			deployment: &Deployment{UID: "dep-uid", Name: "my-deployment", NamespaceUID: "ns-uid", Start: start.Add(-time.Hour), End: end},
+			wantErr:    checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name:       "valid",
+			deployment: &Deployment{UID: "dep-uid", Name: "my-deployment", NamespaceUID: "ns-uid", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.deployment.ValidateDeployment(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterDeployment(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newDeployment := func(uid, name string) *Deployment {
+		return &Deployment{UID: uid, Name: name, NamespaceUID: "ns-uid", Start: start, End: end}
+	}
+	withCluster := func(kms *KubeModelSet) {
+		kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
+	}
+
+	tests := []struct {
+		name       string
+		setup      func(*KubeModelSet)
+		deployment *Deployment
+		wantErr    string
+		want       *KubeModelSet
+	}{
+		{
+			name:       "validation failure",
+			deployment: &Deployment{UID: "", Name: "my-deployment", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:    "RegisterDeployment: invalid deployment: UID is missing for Deployment with name 'my-deployment'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterDeployment: invalid deployment: UID is missing for Deployment with name 'my-deployment'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:       "warns when cluster is nil",
+			deployment: newDeployment("dep-uid", "my-deployment"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Deployments["dep-uid"] = newDeployment("dep-uid", "my-deployment")
+				kms.Metadata.ObjectCount = 1
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelWarning, Message: "RegisterDeployment: Cluster is nil"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:       "registers deployment with cluster",
+			setup:      withCluster,
+			deployment: newDeployment("dep-uid", "my-deployment"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.Deployments["dep-uid"] = newDeployment("dep-uid", "my-deployment")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				withCluster(kms)
+				kms.RegisterDeployment(newDeployment("dep-uid", "original"))
+			},
+			deployment: newDeployment("dep-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.Deployments["dep-uid"] = newDeployment("dep-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterDeployment(tt.deployment)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 148 - 0
core/pkg/model/kubemodel/job_test.go

@@ -0,0 +1,148 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateJob(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name    string
+		job     *Job
+		wantErr string
+	}{
+		{
+			name:    "empty UID",
+			job:     &Job{Name: "my-job", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "UID is missing for Job with name 'my-job'",
+		},
+		{
+			name:    "empty Name",
+			job:     &Job{UID: "job-uid", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "Name is missing for Job 'job-uid'",
+		},
+		{
+			name:    "empty NamespaceUID",
+			job:     &Job{UID: "job-uid", Name: "my-job", Start: start, End: end},
+			wantErr: "NamespaceUID is missing for Job 'job-uid'",
+		},
+		{
+			name:    "outside window",
+			job:     &Job{UID: "job-uid", Name: "my-job", NamespaceUID: "ns-uid", Start: start.Add(-time.Hour), End: end},
+			wantErr: checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name: "valid",
+			job:  &Job{UID: "job-uid", Name: "my-job", NamespaceUID: "ns-uid", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.job.ValidateJob(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterJob(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newJob := func(uid, name string) *Job {
+		return &Job{UID: uid, Name: name, NamespaceUID: "ns-uid", Start: start, End: end}
+	}
+	withCluster := func(kms *KubeModelSet) {
+		kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
+	}
+
+	tests := []struct {
+		name    string
+		setup   func(*KubeModelSet)
+		job     *Job
+		wantErr string
+		want    *KubeModelSet
+	}{
+		{
+			name:    "validation failure",
+			job:     &Job{UID: "", Name: "my-job", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "RegisterJob: invalid job: UID is missing for Job with name 'my-job'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterJob: invalid job: UID is missing for Job with name 'my-job'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name: "warns when cluster is nil",
+			job:  newJob("job-uid", "my-job"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Jobs["job-uid"] = newJob("job-uid", "my-job")
+				kms.Metadata.ObjectCount = 1
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelWarning, Message: "RegisterJob: Cluster is nil"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:  "registers job with cluster",
+			setup: withCluster,
+			job:   newJob("job-uid", "my-job"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.Jobs["job-uid"] = newJob("job-uid", "my-job")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				withCluster(kms)
+				kms.RegisterJob(newJob("job-uid", "original"))
+			},
+			job: newJob("job-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.Jobs["job-uid"] = newJob("job-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterJob(tt.job)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 143 - 0
core/pkg/model/kubemodel/namespace_test.go

@@ -0,0 +1,143 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateNamespace(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name      string
+		namespace *Namespace
+		wantErr   string
+	}{
+		{
+			name:      "empty UID",
+			namespace: &Namespace{Name: "my-ns", Start: start, End: end},
+			wantErr:   "UID is missing for Namespace with name 'my-ns'",
+		},
+		{
+			name:      "empty Name",
+			namespace: &Namespace{UID: "ns-uid", Start: start, End: end},
+			wantErr:   "Name is missing for Namespace 'ns-uid'",
+		},
+		{
+			name:      "outside window",
+			namespace: &Namespace{UID: "ns-uid", Name: "my-ns", Start: start.Add(-time.Hour), End: end},
+			wantErr:   checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name:      "valid",
+			namespace: &Namespace{UID: "ns-uid", Name: "my-ns", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.namespace.ValidateNamespace(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterNamespace(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newNamespace := func(uid, name string) *Namespace {
+		return &Namespace{UID: uid, Name: name, Start: start, End: end}
+	}
+	withCluster := func(kms *KubeModelSet) {
+		kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
+	}
+
+	tests := []struct {
+		name      string
+		setup     func(*KubeModelSet)
+		namespace *Namespace
+		wantErr   string
+		want      *KubeModelSet
+	}{
+		{
+			name:      "validation failure",
+			namespace: &Namespace{UID: "", Name: "my-ns", Start: start, End: end},
+			wantErr:   "RegisterNamespace: invalid namespace: UID is missing for Namespace with name 'my-ns'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterNamespace: invalid namespace: UID is missing for Namespace with name 'my-ns'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:      "warns when cluster is nil",
+			namespace: newNamespace("ns-uid", "my-ns"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Namespaces["ns-uid"] = newNamespace("ns-uid", "my-ns")
+				kms.Metadata.ObjectCount = 1
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelWarning, Message: "RegisterNamespace: Cluster is nil"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:      "registers namespace with cluster",
+			setup:     withCluster,
+			namespace: newNamespace("ns-uid", "my-ns"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.Namespaces["ns-uid"] = newNamespace("ns-uid", "my-ns")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				withCluster(kms)
+				kms.RegisterNamespace(newNamespace("ns-uid", "original"))
+			},
+			namespace: newNamespace("ns-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.Namespaces["ns-uid"] = newNamespace("ns-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterNamespace(tt.namespace)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 143 - 0
core/pkg/model/kubemodel/node_test.go

@@ -0,0 +1,143 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateNode(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name    string
+		node    *Node
+		wantErr string
+	}{
+		{
+			name:    "empty UID",
+			node:    &Node{Name: "my-node", Start: start, End: end},
+			wantErr: "UID is missing for Node with name 'my-node'",
+		},
+		{
+			name:    "empty Name",
+			node:    &Node{UID: "node-uid", Start: start, End: end},
+			wantErr: "Name is missing for Node 'node-uid'",
+		},
+		{
+			name:    "outside window",
+			node:    &Node{UID: "node-uid", Name: "my-node", Start: start.Add(-time.Hour), End: end},
+			wantErr: checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name: "valid",
+			node: &Node{UID: "node-uid", Name: "my-node", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.node.ValidateNode(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterNode(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newNode := func(uid, name string) *Node {
+		return &Node{UID: uid, Name: name, Start: start, End: end}
+	}
+	withCluster := func(kms *KubeModelSet) {
+		kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
+	}
+
+	tests := []struct {
+		name    string
+		setup   func(*KubeModelSet)
+		node    *Node
+		wantErr string
+		want    *KubeModelSet
+	}{
+		{
+			name:    "validation failure",
+			node:    &Node{UID: "", Name: "my-node", Start: start, End: end},
+			wantErr: "RegisterNode: invalid node: UID is missing for Node with name 'my-node'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterNode: invalid node: UID is missing for Node with name 'my-node'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name: "warns when cluster is nil",
+			node: newNode("node-uid", "my-node"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Nodes["node-uid"] = newNode("node-uid", "my-node")
+				kms.Metadata.ObjectCount = 1
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelWarning, Message: "RegisterNode: Cluster is nil"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:  "registers node with cluster",
+			setup: withCluster,
+			node:  newNode("node-uid", "my-node"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.Nodes["node-uid"] = newNode("node-uid", "my-node")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				withCluster(kms)
+				kms.RegisterNode(newNode("node-uid", "original"))
+			},
+			node: newNode("node-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.Nodes["node-uid"] = newNode("node-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterNode(tt.node)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 123 - 0
core/pkg/model/kubemodel/pv_test.go

@@ -0,0 +1,123 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidatePersistentVolume(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name    string
+		pv      *PersistentVolume
+		wantErr string
+	}{
+		{
+			name:    "empty UID",
+			pv:      &PersistentVolume{Name: "my-pv", Start: start, End: end},
+			wantErr: "UID is missing for PersistentVolume with name 'my-pv'",
+		},
+		{
+			name:    "empty Name",
+			pv:      &PersistentVolume{UID: "pv-uid", Start: start, End: end},
+			wantErr: "Name is missing for PersistentVolume 'pv-uid'",
+		},
+		{
+			name:    "outside window",
+			pv:      &PersistentVolume{UID: "pv-uid", Name: "my-pv", Start: start.Add(-time.Hour), End: end},
+			wantErr: checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name: "valid",
+			pv:   &PersistentVolume{UID: "pv-uid", Name: "my-pv", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.pv.ValidatePersistentVolume(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterPersistentVolume(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newPV := func(uid, name string) *PersistentVolume {
+		return &PersistentVolume{UID: uid, Name: name, Start: start, End: end}
+	}
+
+	tests := []struct {
+		name    string
+		setup   func(*KubeModelSet)
+		pv      *PersistentVolume
+		wantErr string
+		want    *KubeModelSet
+	}{
+		{
+			name:    "validation failure",
+			pv:      &PersistentVolume{UID: "", Name: "my-pv", Start: start, End: end},
+			wantErr: "RegisterPersistentVolume: invalid persistent volume: UID is missing for PersistentVolume with name 'my-pv'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterPersistentVolume: invalid persistent volume: UID is missing for PersistentVolume with name 'my-pv'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name: "registers persistent volume",
+			pv:   newPV("pv-uid", "my-pv"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.PersistentVolumes["pv-uid"] = newPV("pv-uid", "my-pv")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				kms.RegisterPersistentVolume(newPV("pv-uid", "original"))
+			},
+			pv: newPV("pv-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.PersistentVolumes["pv-uid"] = newPV("pv-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterPersistentVolume(tt.pv)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 128 - 0
core/pkg/model/kubemodel/pvc_test.go

@@ -0,0 +1,128 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidatePVC(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name    string
+		pvc     *PersistentVolumeClaim
+		wantErr string
+	}{
+		{
+			name:    "empty UID",
+			pvc:     &PersistentVolumeClaim{Name: "my-pvc", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "UID is missing for PVC with name 'my-pvc'",
+		},
+		{
+			name:    "empty Name",
+			pvc:     &PersistentVolumeClaim{UID: "pvc-uid", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "Name is missing for PVC 'pvc-uid'",
+		},
+		{
+			name:    "empty NamespaceUID",
+			pvc:     &PersistentVolumeClaim{UID: "pvc-uid", Name: "my-pvc", Start: start, End: end},
+			wantErr: "NamespaceUID is missing for PVC 'pvc-uid'",
+		},
+		{
+			name:    "outside window",
+			pvc:     &PersistentVolumeClaim{UID: "pvc-uid", Name: "my-pvc", NamespaceUID: "ns-uid", Start: start.Add(-time.Hour), End: end},
+			wantErr: checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name: "valid",
+			pvc:  &PersistentVolumeClaim{UID: "pvc-uid", Name: "my-pvc", NamespaceUID: "ns-uid", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.pvc.ValidatePVC(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterPVC(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newPVC := func(uid, name string) *PersistentVolumeClaim {
+		return &PersistentVolumeClaim{UID: uid, Name: name, NamespaceUID: "ns-uid", Start: start, End: end}
+	}
+
+	tests := []struct {
+		name    string
+		setup   func(*KubeModelSet)
+		pvc     *PersistentVolumeClaim
+		wantErr string
+		want    *KubeModelSet
+	}{
+		{
+			name:    "validation failure",
+			pvc:     &PersistentVolumeClaim{UID: "", Name: "my-pvc", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "RegisterPVC: invalid pvc: UID is missing for PVC with name 'my-pvc'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterPVC: invalid pvc: UID is missing for PVC with name 'my-pvc'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name: "registers pvc",
+			pvc:  newPVC("pvc-uid", "my-pvc"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.PersistentVolumeClaims["pvc-uid"] = newPVC("pvc-uid", "my-pvc")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				kms.RegisterPVC(newPVC("pvc-uid", "original"))
+			},
+			pvc: newPVC("pvc-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.PersistentVolumeClaims["pvc-uid"] = newPVC("pvc-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterPVC(tt.pvc)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 148 - 0
core/pkg/model/kubemodel/replicaset_test.go

@@ -0,0 +1,148 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateReplicaSet(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name       string
+		replicaSet *ReplicaSet
+		wantErr    string
+	}{
+		{
+			name:       "empty UID",
+			replicaSet: &ReplicaSet{Name: "my-rs", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:    "UID is missing for ReplicaSet with name 'my-rs'",
+		},
+		{
+			name:       "empty Name",
+			replicaSet: &ReplicaSet{UID: "rs-uid", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:    "Name is missing for ReplicaSet 'rs-uid'",
+		},
+		{
+			name:       "empty NamespaceUID",
+			replicaSet: &ReplicaSet{UID: "rs-uid", Name: "my-rs", Start: start, End: end},
+			wantErr:    "NamespaceUID is missing for ReplicaSet 'rs-uid'",
+		},
+		{
+			name:       "outside window",
+			replicaSet: &ReplicaSet{UID: "rs-uid", Name: "my-rs", NamespaceUID: "ns-uid", Start: start.Add(-time.Hour), End: end},
+			wantErr:    checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name:       "valid",
+			replicaSet: &ReplicaSet{UID: "rs-uid", Name: "my-rs", NamespaceUID: "ns-uid", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.replicaSet.ValidateReplicaSet(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterReplicaSet(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newReplicaSet := func(uid, name string) *ReplicaSet {
+		return &ReplicaSet{UID: uid, Name: name, NamespaceUID: "ns-uid", Start: start, End: end}
+	}
+	withCluster := func(kms *KubeModelSet) {
+		kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
+	}
+
+	tests := []struct {
+		name       string
+		setup      func(*KubeModelSet)
+		replicaSet *ReplicaSet
+		wantErr    string
+		want       *KubeModelSet
+	}{
+		{
+			name:       "validation failure",
+			replicaSet: &ReplicaSet{UID: "", Name: "my-rs", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:    "RegisterReplicaSet: invalid replicaset: UID is missing for ReplicaSet with name 'my-rs'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterReplicaSet: invalid replicaset: UID is missing for ReplicaSet with name 'my-rs'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:       "warns when cluster is nil",
+			replicaSet: newReplicaSet("rs-uid", "my-rs"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.ReplicaSets["rs-uid"] = newReplicaSet("rs-uid", "my-rs")
+				kms.Metadata.ObjectCount = 1
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelWarning, Message: "RegisterReplicaSet: Cluster is nil"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:       "registers replicaset with cluster",
+			setup:      withCluster,
+			replicaSet: newReplicaSet("rs-uid", "my-rs"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.ReplicaSets["rs-uid"] = newReplicaSet("rs-uid", "my-rs")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				withCluster(kms)
+				kms.RegisterReplicaSet(newReplicaSet("rs-uid", "original"))
+			},
+			replicaSet: newReplicaSet("rs-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.ReplicaSets["rs-uid"] = newReplicaSet("rs-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterReplicaSet(tt.replicaSet)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 135 - 0
core/pkg/model/kubemodel/resourcequota_test.go

@@ -0,0 +1,135 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateResourceQuota(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name          string
+		resourceQuota *ResourceQuota
+		wantErr       string
+	}{
+		{
+			name:          "empty UID",
+			resourceQuota: &ResourceQuota{Name: "my-rq", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:       "UID is missing for ResourceQuota with name 'my-rq'",
+		},
+		{
+			name:          "empty Name",
+			resourceQuota: &ResourceQuota{UID: "rq-uid", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:       "Name is missing for ResourceQuota 'rq-uid'",
+		},
+		{
+			name:          "empty NamespaceUID",
+			resourceQuota: &ResourceQuota{UID: "rq-uid", Name: "my-rq", Start: start, End: end},
+			wantErr:       "NamespaceUID is missing for ResourceQuota 'rq-uid'",
+		},
+		{
+			name:          "outside window",
+			resourceQuota: &ResourceQuota{UID: "rq-uid", Name: "my-rq", NamespaceUID: "ns-uid", Start: start.Add(-time.Hour), End: end},
+			wantErr:       checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name:          "valid",
+			resourceQuota: &ResourceQuota{UID: "rq-uid", Name: "my-rq", NamespaceUID: "ns-uid", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.resourceQuota.ValidateResourceQuota(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterResourceQuota(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newRQ := func(uid, name string) *ResourceQuota {
+		return &ResourceQuota{UID: uid, Name: name, NamespaceUID: "ns-uid", Start: start, End: end}
+	}
+	// RegisterResourceQuota initializes nil Spec/Status on registration.
+	newRegisteredRQ := func(uid, name string) *ResourceQuota {
+		rq := newRQ(uid, name)
+		rq.Spec = &ResourceQuotaSpec{Hard: &ResourceQuotaSpecHard{}}
+		rq.Status = &ResourceQuotaStatus{Used: &ResourceQuotaStatusUsed{}}
+		return rq
+	}
+
+	tests := []struct {
+		name          string
+		setup         func(*KubeModelSet)
+		resourceQuota *ResourceQuota
+		wantErr       string
+		want          *KubeModelSet
+	}{
+		{
+			name:          "validation failure",
+			resourceQuota: &ResourceQuota{UID: "", Name: "my-rq", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:       "RegisterResourceQuota: invalid resource quota: UID is missing for ResourceQuota with name 'my-rq'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterResourceQuota: invalid resource quota: UID is missing for ResourceQuota with name 'my-rq'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:          "registers resource quota",
+			resourceQuota: newRQ("rq-uid", "my-rq"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.ResourceQuotas["rq-uid"] = newRegisteredRQ("rq-uid", "my-rq")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				kms.RegisterResourceQuota(newRQ("rq-uid", "original"))
+			},
+			resourceQuota: newRQ("rq-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.ResourceQuotas["rq-uid"] = newRegisteredRQ("rq-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterResourceQuota(tt.resourceQuota)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}

+ 123 - 0
core/pkg/model/kubemodel/service_test.go

@@ -2,8 +2,131 @@ package kubemodel
 
 import (
 	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
 )
 
+func TestValidateService(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name    string
+		service *Service
+		wantErr string
+	}{
+		{
+			name:    "empty UID",
+			service: &Service{Name: "my-svc", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "UID is missing for Service with name 'my-svc'",
+		},
+		{
+			name:    "empty Name",
+			service: &Service{UID: "svc-uid", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "Name is missing for Service 'svc-uid'",
+		},
+		{
+			name:    "empty NamespaceUID",
+			service: &Service{UID: "svc-uid", Name: "my-svc", Start: start, End: end},
+			wantErr: "NamespaceUID is missing for Service 'svc-uid'",
+		},
+		{
+			name:    "outside window",
+			service: &Service{UID: "svc-uid", Name: "my-svc", NamespaceUID: "ns-uid", Start: start.Add(-time.Hour), End: end},
+			wantErr: checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name:    "valid",
+			service: &Service{UID: "svc-uid", Name: "my-svc", NamespaceUID: "ns-uid", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.service.ValidateService(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterService(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newService := func(uid, name string) *Service {
+		return &Service{UID: uid, Name: name, NamespaceUID: "ns-uid", Start: start, End: end}
+	}
+
+	tests := []struct {
+		name    string
+		setup   func(*KubeModelSet)
+		service *Service
+		wantErr string
+		want    *KubeModelSet
+	}{
+		{
+			name:    "validation failure",
+			service: &Service{UID: "", Name: "my-svc", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr: "RegisterService: invalid service: UID is missing for Service with name 'my-svc'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterService: invalid service: UID is missing for Service with name 'my-svc'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:    "registers service",
+			service: newService("svc-uid", "my-svc"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Services["svc-uid"] = newService("svc-uid", "my-svc")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				kms.RegisterService(newService("svc-uid", "original"))
+			},
+			service: newService("svc-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Services["svc-uid"] = newService("svc-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterService(tt.service)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}
+
 func TestParseServiceType(t *testing.T) {
 	tests := []struct {
 		input    string

+ 148 - 0
core/pkg/model/kubemodel/statefulset_test.go

@@ -0,0 +1,148 @@
+package kubemodel
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateStatefulSet(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+	window := Window{Start: start, End: end}
+
+	tests := []struct {
+		name        string
+		statefulSet *StatefulSet
+		wantErr     string
+	}{
+		{
+			name:        "empty UID",
+			statefulSet: &StatefulSet{Name: "my-sts", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:     "UID is missing for StatefulSet with name 'my-sts'",
+		},
+		{
+			name:        "empty Name",
+			statefulSet: &StatefulSet{UID: "sts-uid", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:     "Name is missing for StatefulSet 'sts-uid'",
+		},
+		{
+			name:        "empty NamespaceUID",
+			statefulSet: &StatefulSet{UID: "sts-uid", Name: "my-sts", Start: start, End: end},
+			wantErr:     "NamespaceUID is missing for StatefulSet 'sts-uid'",
+		},
+		{
+			name:        "outside window",
+			statefulSet: &StatefulSet{UID: "sts-uid", Name: "my-sts", NamespaceUID: "ns-uid", Start: start.Add(-time.Hour), End: end},
+			wantErr:     checkWindow(window, start.Add(-time.Hour), end).Error(),
+		},
+		{
+			name:        "valid",
+			statefulSet: &StatefulSet{UID: "sts-uid", Name: "my-sts", NamespaceUID: "ns-uid", Start: start, End: end},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			err := tt.statefulSet.ValidateStatefulSet(window)
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
+
+func TestRegisterStatefulSet(t *testing.T) {
+	start := time.Now().UTC().Truncate(time.Hour)
+	end := start.Add(time.Hour)
+
+	newStatefulSet := func(uid, name string) *StatefulSet {
+		return &StatefulSet{UID: uid, Name: name, NamespaceUID: "ns-uid", Start: start, End: end}
+	}
+	withCluster := func(kms *KubeModelSet) {
+		kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
+	}
+
+	tests := []struct {
+		name        string
+		setup       func(*KubeModelSet)
+		statefulSet *StatefulSet
+		wantErr     string
+		want        *KubeModelSet
+	}{
+		{
+			name:        "validation failure",
+			statefulSet: &StatefulSet{UID: "", Name: "my-sts", NamespaceUID: "ns-uid", Start: start, End: end},
+			wantErr:     "RegisterStatefulSet: invalid statefulset: UID is missing for StatefulSet with name 'my-sts'",
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelError, Message: "RegisterStatefulSet: invalid statefulset: UID is missing for StatefulSet with name 'my-sts'"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:        "warns when cluster is nil",
+			statefulSet: newStatefulSet("sts-uid", "my-sts"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				kms.StatefulSets["sts-uid"] = newStatefulSet("sts-uid", "my-sts")
+				kms.Metadata.ObjectCount = 1
+				kms.Metadata.Diagnostics = []Diagnostic{
+					{Level: DiagnosticLevelWarning, Message: "RegisterStatefulSet: Cluster is nil"},
+				}
+				return kms
+			}(),
+		},
+		{
+			name:        "registers statefulset with cluster",
+			setup:       withCluster,
+			statefulSet: newStatefulSet("sts-uid", "my-sts"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.StatefulSets["sts-uid"] = newStatefulSet("sts-uid", "my-sts")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+		{
+			name: "duplicate registration is a no-op",
+			setup: func(kms *KubeModelSet) {
+				withCluster(kms)
+				kms.RegisterStatefulSet(newStatefulSet("sts-uid", "original"))
+			},
+			statefulSet: newStatefulSet("sts-uid", "duplicate"),
+			want: func() *KubeModelSet {
+				kms := NewKubeModelSet(start, end)
+				withCluster(kms)
+				kms.StatefulSets["sts-uid"] = newStatefulSet("sts-uid", "original")
+				kms.Metadata.ObjectCount = 1
+				return kms
+			}(),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kms := NewKubeModelSet(start, end)
+			if tt.setup != nil {
+				tt.setup(kms)
+			}
+
+			err := kms.RegisterStatefulSet(tt.statefulSet)
+
+			if tt.wantErr != "" {
+				require.EqualError(t, err, tt.wantErr)
+			} else {
+				require.NoError(t, err)
+			}
+
+			KubeModelSetEquals(t, tt.want, kms)
+		})
+	}
+}