package vmware import ( "testing" "time" "github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources" ) func TestProviderRecords_ProjectCanonicalVMwareResources(t *testing.T) { previous := IsFeatureEnabled() SetFeatureEnabled(true) t.Cleanup(func() { SetFeatureEnabled(previous) }) collectedAt := time.Date(2026, time.March, 30, 18, 15, 0, 0, time.UTC) provider := NewProvider(InventorySnapshot{ ConnectionID: "vc-1", ConnectionName: "Lab VC", VCenterHost: "vc.lab.local", CollectedAt: collectedAt, Hosts: []InventoryHost{{ Host: "host-101", Name: "esxi-01.lab.local", ConnectionState: "CONNECTED", PowerState: "POWERED_ON", HostUUID: "uuid-host-1", DatacenterID: "datacenter-1", DatacenterName: "DC1", ComputeResourceID: "domain-c101", ComputeResourceName: "Prod Compute", ClusterID: "domain-c101", ClusterName: "Prod Compute", FolderID: "group-h4", FolderName: "Prod Hosts", DatastoreIDs: []string{"datastore-11"}, DatastoreNames: []string{"nvme-primary"}, OverallStatus: "yellow", Metrics: &InventoryMetrics{ CPUPercent: float64Ptr(21.4), MemoryPercent: float64Ptr(63.2), MemoryUsedBytes: int64Ptr(27144105984), MemoryTotalBytes: int64Ptr(42949672960), NetInBytesPerSecond: float64Ptr(1024), NetOutBytesPerSecond: float64Ptr(2048), DiskReadBytesPerSecond: float64Ptr(4096), DiskWriteBytesPerSecond: float64Ptr(8192), }, TriggeredAlarms: []InventoryAlarm{{ Alarm: "alarm-11", Name: "Host connection degraded", OverallStatus: "yellow", TriggeredAt: collectedAt.Add(-2 * time.Minute), }}, RecentTasks: []InventoryTask{{ Task: "task-11", Name: "Reconnect host", State: "running", StartedAt: collectedAt.Add(-1 * time.Minute), }}, }}, VMs: []InventoryVM{{ VM: "vm-201", Name: "app-01", PowerState: "POWERED_ON", CPUCount: 4, MemorySizeMiB: 8192, DatacenterID: "datacenter-1", DatacenterName: "DC1", ComputeResourceID: "domain-c101", ComputeResourceName: "Prod Compute", ClusterID: "domain-c101", ClusterName: "Prod Compute", FolderID: "group-v7", FolderName: "Production VMs", ResourcePoolID: "resgroup-22", ResourcePoolName: "Tier 1", RuntimeHostID: "host-101", RuntimeHostName: "esxi-01.lab.local", DatastoreIDs: []string{"datastore-11"}, DatastoreNames: []string{"nvme-primary"}, InstanceUUID: "vm-instance-201", BIOSUUID: "vm-bios-201", GuestOSFamily: "LINUX", GuestHostname: "app-01.internal", GuestIPAddresses: []string{"10.0.0.21"}, OverallStatus: "green", Metrics: &InventoryMetrics{ CPUPercent: float64Ptr(38.1), MemoryPercent: float64Ptr(57.5), MemoryUsedBytes: int64Ptr(5033164800), MemoryTotalBytes: int64Ptr(8589934592), NetInBytesPerSecond: float64Ptr(512), NetOutBytesPerSecond: float64Ptr(768), DiskReadBytesPerSecond: float64Ptr(1536), DiskWriteBytesPerSecond: float64Ptr(2048), }, TriggeredAlarms: []InventoryAlarm{{ Alarm: "alarm-21", Name: "VM replication fault", OverallStatus: "red", TriggeredAt: collectedAt.Add(-3 * time.Minute), }}, RecentTasks: []InventoryTask{{ Task: "task-21", Name: "Create snapshot", State: "success", StartedAt: collectedAt.Add(-5 * time.Minute), }}, SnapshotCount: 2, }}, Datastores: []InventoryDatastore{{ Datastore: "datastore-11", Name: "nvme-primary", Type: "VMFS", FreeSpace: 40, Capacity: 100, DatacenterID: "datacenter-1", DatacenterName: "DC1", FolderID: "group-s4", FolderName: "Shared Datastores", HostIDs: []string{"host-101"}, HostNames: []string{"esxi-01.lab.local"}, VMIDs: []string{"vm-201"}, VMNames: []string{"app-01"}, Accessible: boolPtr(true), MultipleHostAccess: boolPtr(true), MaintenanceMode: "normal", URL: "ds:///vmfs/volumes/datastore-11/", OverallStatus: "yellow", RecentTasks: []InventoryTask{{ Task: "task-31", Name: "Refresh datastore", State: "queued", StartedAt: collectedAt.Add(-4 * time.Minute), }}, }}, }) records := provider.Records() if len(records) != 3 { t.Fatalf("expected 3 VMware records, got %d", len(records)) } hostRecord := records[0] if hostRecord.SourceID != "vc-1:host:host-101" { t.Fatalf("host source id = %q, want vc-1:host:host-101", hostRecord.SourceID) } if hostRecord.Resource.Type != unifiedresources.ResourceTypeAgent { t.Fatalf("host resource type = %q, want %q", hostRecord.Resource.Type, unifiedresources.ResourceTypeAgent) } if hostRecord.Resource.Status != unifiedresources.StatusWarning { t.Fatalf("host status = %q, want %q", hostRecord.Resource.Status, unifiedresources.StatusWarning) } if hostRecord.Resource.VMware == nil || hostRecord.Resource.VMware.ManagedObjectID != "host-101" { t.Fatalf("expected VMware host metadata, got %+v", hostRecord.Resource.VMware) } if got := hostRecord.Resource.VMware.ActiveAlarmCount; got != 1 { t.Fatalf("host active alarm count = %d, want 1", got) } if got := hostRecord.Resource.VMware.RecentTaskSummary; got != "Reconnect host (running)" { t.Fatalf("host recent task summary = %q, want %q", got, "Reconnect host (running)") } if got := hostRecord.Resource.VMware.ClusterName; got != "Prod Compute" { t.Fatalf("host cluster name = %q, want Prod Compute", got) } if got := hostRecord.Resource.VMware.DatastoreNames; len(got) != 1 || got[0] != "nvme-primary" { t.Fatalf("host datastore names = %#v, want [nvme-primary]", got) } if hostRecord.Resource.Metrics == nil || hostRecord.Resource.Metrics.CPU == nil || hostRecord.Resource.Metrics.Memory == nil { t.Fatalf("expected VMware host metrics, got %+v", hostRecord.Resource.Metrics) } if got := hostRecord.Resource.Metrics.CPU.Percent; got != 21.4 { t.Fatalf("host cpu percent = %v, want 21.4", got) } if got := hostRecord.Resource.Metrics.NetOut.Value; got != 2048 { t.Fatalf("host net out = %v, want 2048", got) } if len(hostRecord.Resource.Incidents) != 1 || hostRecord.Resource.Incidents[0].Code != "vmware_alarm_state" { t.Fatalf("expected host VMware incident projection, got %+v", hostRecord.Resource.Incidents) } if hostRecord.Identity.DMIUUID != "uuid-host-1" { t.Fatalf("host identity DMI UUID = %q, want uuid-host-1", hostRecord.Identity.DMIUUID) } if got := hostRecord.Identity.ClusterName; got != "Prod Compute" { t.Fatalf("host cluster name = %q, want Prod Compute", got) } if hostRecord.Resource.LastSeen != collectedAt { t.Fatalf("host last seen = %v, want %v", hostRecord.Resource.LastSeen, collectedAt) } vmRecord := records[1] if vmRecord.SourceID != "vc-1:vm:vm-201" { t.Fatalf("vm source id = %q, want vc-1:vm:vm-201", vmRecord.SourceID) } if vmRecord.Resource.Type != unifiedresources.ResourceTypeVM { t.Fatalf("vm resource type = %q, want %q", vmRecord.Resource.Type, unifiedresources.ResourceTypeVM) } if vmRecord.Resource.VMware == nil || vmRecord.Resource.VMware.CPUCount != 4 { t.Fatalf("expected VMware VM metadata with cpu count, got %+v", vmRecord.Resource.VMware) } if vmRecord.Resource.Status != unifiedresources.StatusWarning { t.Fatalf("vm status = %q, want %q", vmRecord.Resource.Status, unifiedresources.StatusWarning) } if got := vmRecord.Resource.VMware.ActiveAlarmSummary; got != "VM replication fault (red)" { t.Fatalf("vm active alarm summary = %q, want %q", got, "VM replication fault (red)") } if got := vmRecord.Resource.VMware.SnapshotCount; got != 2 { t.Fatalf("vm snapshot count = %d, want 2", got) } if got := vmRecord.Resource.ParentName; got != "esxi-01.lab.local" { t.Fatalf("vm parent name = %q, want esxi-01.lab.local", got) } if got := vmRecord.ParentSourceID; got != "vc-1:host:host-101" { t.Fatalf("vm parent source id = %q, want vc-1:host:host-101", got) } if got := vmRecord.Resource.VMware.RuntimeHostName; got != "esxi-01.lab.local" { t.Fatalf("vm runtime host name = %q, want esxi-01.lab.local", got) } if got := vmRecord.Resource.VMware.InstanceUUID; got != "vm-instance-201" { t.Fatalf("vm instance uuid = %q, want vm-instance-201", got) } if got := vmRecord.Identity.MachineID; got != "vm-instance-201" { t.Fatalf("vm machine id = %q, want vm-instance-201", got) } if got := vmRecord.Identity.IPAddresses; len(got) != 1 || got[0] != "10.0.0.21" { t.Fatalf("vm identity IPs = %#v, want [10.0.0.21]", got) } if vmRecord.Resource.Metrics == nil || vmRecord.Resource.Metrics.CPU == nil || vmRecord.Resource.Metrics.Memory == nil { t.Fatalf("expected VMware VM metrics, got %+v", vmRecord.Resource.Metrics) } if got := vmRecord.Resource.Metrics.Memory.Percent; got != 57.5 { t.Fatalf("vm memory percent = %v, want 57.5", got) } if got := vmRecord.Resource.Metrics.DiskWrite.Value; got != 2048 { t.Fatalf("vm disk write = %v, want 2048", got) } if len(vmRecord.Resource.Incidents) != 1 || vmRecord.Resource.Incidents[0].Code != "vmware_alarm_state" { t.Fatalf("expected VMware VM incident projection, got %+v", vmRecord.Resource.Incidents) } datastoreRecord := records[2] if datastoreRecord.SourceID != "vc-1:datastore:datastore-11" { t.Fatalf("datastore source id = %q, want vc-1:datastore:datastore-11", datastoreRecord.SourceID) } if datastoreRecord.Resource.Type != unifiedresources.ResourceTypeStorage { t.Fatalf("datastore resource type = %q, want %q", datastoreRecord.Resource.Type, unifiedresources.ResourceTypeStorage) } if datastoreRecord.Resource.Storage == nil || datastoreRecord.Resource.Storage.Platform != "vmware-vsphere" { t.Fatalf("expected canonical VMware storage metadata, got %+v", datastoreRecord.Resource.Storage) } if datastoreRecord.Resource.Status != unifiedresources.StatusWarning { t.Fatalf("datastore status = %q, want %q", datastoreRecord.Resource.Status, unifiedresources.StatusWarning) } if datastoreRecord.Resource.Metrics == nil || datastoreRecord.Resource.Metrics.Disk == nil { t.Fatal("expected datastore disk metrics to be populated") } if got := datastoreRecord.Resource.Metrics.Disk.Percent; got != 60 { t.Fatalf("datastore disk usage percent = %v, want 60", got) } if len(datastoreRecord.Resource.Incidents) != 1 || datastoreRecord.Resource.Incidents[0].Code != "vmware_health_state" { t.Fatalf("expected VMware datastore health incident, got %+v", datastoreRecord.Resource.Incidents) } if got := datastoreRecord.Resource.VMware.RecentTaskSummary; got != "Refresh datastore (queued)" { t.Fatalf("datastore recent task summary = %q, want %q", got, "Refresh datastore (queued)") } if !datastoreRecord.Resource.Storage.Shared { t.Fatal("expected datastore storage meta to mark the datastore as shared") } if got := datastoreRecord.Resource.Storage.Nodes; len(got) != 1 || got[0] != "esxi-01.lab.local" { t.Fatalf("datastore storage nodes = %#v, want [esxi-01.lab.local]", got) } if got := datastoreRecord.Resource.Storage.ConsumerCount; got != 1 { t.Fatalf("datastore consumer count = %d, want 1", got) } if got := datastoreRecord.Resource.Storage.TopConsumers; len(got) != 1 || got[0].Name != "app-01" { t.Fatalf("datastore top consumers = %#v, want app-01", got) } if datastoreRecord.Resource.VMware.DatastoreAccessible == nil || !*datastoreRecord.Resource.VMware.DatastoreAccessible { t.Fatalf("datastore accessible = %#v, want true", datastoreRecord.Resource.VMware.DatastoreAccessible) } } func TestProviderActivityChanges_ProjectCanonicalTimelineEntries(t *testing.T) { previous := IsFeatureEnabled() SetFeatureEnabled(true) t.Cleanup(func() { SetFeatureEnabled(previous) }) collectedAt := time.Date(2026, time.March, 30, 18, 20, 0, 0, time.UTC) provider := NewProvider(InventorySnapshot{ ConnectionID: "vc-1", ConnectionName: "Lab VC", VCenterHost: "vc.lab.local", CollectedAt: collectedAt, Hosts: []InventoryHost{{ Host: "host-101", Name: "esxi-01.lab.local", RecentTasks: []InventoryTask{{ Task: "task-11", Name: "Reconnect host", State: "running", StartedAt: collectedAt.Add(-1 * time.Minute), }}, }}, VMs: []InventoryVM{{ VM: "vm-201", Name: "app-01", RecentEvents: []InventoryEvent{{ Event: "event-201", Type: "VmMessageEvent", Message: "Snapshot completed successfully", User: "vpxuser", CreatedAt: collectedAt.Add(-2 * time.Minute), }}, }}, Datastores: []InventoryDatastore{{ Datastore: "datastore-11", Name: "nvme-primary", RecentEvents: []InventoryEvent{{ Event: "event-301", Type: "DatastoreRenamedEvent", Message: "Datastore metadata refreshed", User: "storage-admin", CreatedAt: collectedAt.Add(-3 * time.Minute), }}, }}, }) changes := provider.ActivityChanges() if len(changes) != 3 { t.Fatalf("expected 3 VMware activity changes, got %d", len(changes)) } if changes[0].Kind != unifiedresources.ChangeActivity { t.Fatalf("latest change kind = %q, want %q", changes[0].Kind, unifiedresources.ChangeActivity) } if changes[0].SourceType != unifiedresources.SourcePlatformEvent { t.Fatalf("latest change source type = %q, want %q", changes[0].SourceType, unifiedresources.SourcePlatformEvent) } if changes[0].SourceAdapter != unifiedresources.AdapterVMware { t.Fatalf("latest change source adapter = %q, want %q", changes[0].SourceAdapter, unifiedresources.AdapterVMware) } if changes[0].ResourceID != "vc-1:host:host-101" { t.Fatalf("latest change resource ID = %q, want vc-1:host:host-101", changes[0].ResourceID) } if changes[0].OccurredAt == nil || !changes[0].OccurredAt.Equal(collectedAt.Add(-1*time.Minute)) { t.Fatalf("latest change occurred_at = %v, want %v", changes[0].OccurredAt, collectedAt.Add(-1*time.Minute)) } if got := changes[0].Metadata[unifiedresources.MetadataActivityType]; got != "vmware_task" { t.Fatalf("latest change activity_type = %#v, want vmware_task", got) } if got := changes[1].Metadata["vmwareEventType"]; got != "VmMessageEvent" { t.Fatalf("vm change event type = %#v, want VmMessageEvent", got) } if got := changes[2].Metadata["vmwareEventUser"]; got != "storage-admin" { t.Fatalf("datastore change user = %#v, want storage-admin", got) } } func boolPtr(value bool) *bool { return &value }