package tasks import ( "context" "log/slog" "testing" ) func TestProxmoxReconcileHandlerWritesAuditForDrift(t *testing.T) { client := &stubReconcileClient{status: ProxmoxVMStatus{Status: "stopped"}} auditWriter := &stubAuditWriter{} handler := NewProxmoxReconcileHandler( &stubReconcileClusterStore{clusterIDs: []string{"cluster-1"}}, &stubReconcileVMStore{vms: []ReconcileVM{{ ID: "vm-1", TenantID: "tenant-1", Node: "pve", ProxmoxVMID: 100, Status: "running", }}}, &stubReconcileClientResolver{client: client}, auditWriter, slog.Default(), ) if err := handler.ProcessTask(context.Background(), NewProxmoxReconcileAllTask()); err != nil { t.Fatalf("ProcessTask() error = %v", err) } if client.node != "pve" { t.Fatalf("client node = %q, want pve", client.node) } if auditWriter.event.Action != "vm.reconcile.drift" { t.Fatalf("audit action = %q, want vm.reconcile.drift", auditWriter.event.Action) } if auditWriter.event.Metadata["db_status"] != "running" { t.Fatalf("audit db_status = %v, want running", auditWriter.event.Metadata["db_status"]) } if auditWriter.event.Metadata["proxmox_status"] != "stopped" { t.Fatalf("audit proxmox_status = %v, want stopped", auditWriter.event.Metadata["proxmox_status"]) } } func TestProxmoxReconcileHandlerSkipsMatchingStatus(t *testing.T) { auditWriter := &stubAuditWriter{} handler := NewProxmoxReconcileHandler( &stubReconcileClusterStore{clusterIDs: []string{"cluster-1"}}, &stubReconcileVMStore{vms: []ReconcileVM{{ ID: "vm-1", TenantID: "tenant-1", Node: "pve", ProxmoxVMID: 100, Status: "running", }}}, &stubReconcileClientResolver{client: &stubReconcileClient{status: ProxmoxVMStatus{Status: "running"}}}, auditWriter, slog.Default(), ) if err := handler.ProcessTask(context.Background(), NewProxmoxReconcileAllTask()); err != nil { t.Fatalf("ProcessTask() error = %v", err) } if auditWriter.event.Action != "" { t.Fatalf("audit action = %q, want empty", auditWriter.event.Action) } } func TestNormalizeProxmoxVMStatus(t *testing.T) { tests := []struct { name string status string want string }{ {name: "running", status: "running", want: "running"}, {name: "stopped", status: "stopped", want: "stopped"}, {name: "paused", status: "paused", want: "suspended"}, {name: "unknown", status: "unknown", want: ""}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if got := normalizeProxmoxVMStatus(test.status); got != test.want { t.Fatalf("normalizeProxmoxVMStatus() = %q, want %q", got, test.want) } }) } } type stubReconcileClusterStore struct { clusterIDs []string } func (s *stubReconcileClusterStore) ListActiveClusterIDs(context.Context) ([]string, error) { return s.clusterIDs, nil } type stubReconcileVMStore struct { vms []ReconcileVM } func (s *stubReconcileVMStore) ListClusterVMs(context.Context, string) ([]ReconcileVM, error) { return s.vms, nil } type stubReconcileClientResolver struct { client ProxmoxReconcileClient } func (s *stubReconcileClientResolver) ResolveReconcileClient(context.Context, string) (ProxmoxReconcileClient, error) { return s.client, nil } type stubReconcileClient struct { node string vmid int status ProxmoxVMStatus } func (s *stubReconcileClient) GetVMStatus(_ context.Context, node string, vmid int) (ProxmoxVMStatus, error) { s.node = node s.vmid = vmid return s.status, nil }