mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-22 19:23:31 +00:00
3b5c1347d1
Test coverage for isCephStorageType, countServiceDaemons, extractCephCheckSummary, and summarizeCephHealth functions. These parse Ceph storage types and health status JSON.
384 lines
8.5 KiB
Go
384 lines
8.5 KiB
Go
package monitoring
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/pkg/proxmox"
|
|
)
|
|
|
|
func TestIsCephStorageType(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
storageType string
|
|
want bool
|
|
}{
|
|
// Valid Ceph types
|
|
{"rbd lowercase", "rbd", true},
|
|
{"cephfs lowercase", "cephfs", true},
|
|
{"ceph lowercase", "ceph", true},
|
|
|
|
// Case variations
|
|
{"RBD uppercase", "RBD", true},
|
|
{"CephFS mixed case", "CephFS", true},
|
|
{"CEPH uppercase", "CEPH", true},
|
|
|
|
// Whitespace handling
|
|
{"rbd with leading space", " rbd", true},
|
|
{"rbd with trailing space", "rbd ", true},
|
|
{"rbd with surrounding spaces", " rbd ", true},
|
|
|
|
// Non-Ceph storage types
|
|
{"local storage", "local", false},
|
|
{"dir storage", "dir", false},
|
|
{"nfs storage", "nfs", false},
|
|
{"lvm storage", "lvm", false},
|
|
{"zfs storage", "zfs", false},
|
|
{"zfspool storage", "zfspool", false},
|
|
{"iscsi storage", "iscsi", false},
|
|
|
|
// Edge cases
|
|
{"empty string", "", false},
|
|
{"whitespace only", " ", false},
|
|
{"partial match rbd", "rbd-pool", false},
|
|
{"partial match ceph", "ceph-pool", false},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if got := isCephStorageType(tc.storageType); got != tc.want {
|
|
t.Fatalf("isCephStorageType(%q) = %v, want %v", tc.storageType, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCountServiceDaemons(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
services map[string]proxmox.CephServiceDefinition
|
|
serviceType string
|
|
want int
|
|
}{
|
|
{
|
|
name: "nil services map",
|
|
services: nil,
|
|
serviceType: "mon",
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "empty services map",
|
|
services: map[string]proxmox.CephServiceDefinition{},
|
|
serviceType: "mon",
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "service type not found",
|
|
services: map[string]proxmox.CephServiceDefinition{
|
|
"mon": {
|
|
Daemons: map[string]proxmox.CephServiceDaemon{
|
|
"a": {Host: "node1", Status: "running"},
|
|
},
|
|
},
|
|
},
|
|
serviceType: "osd",
|
|
want: 0,
|
|
},
|
|
{
|
|
name: "single daemon",
|
|
services: map[string]proxmox.CephServiceDefinition{
|
|
"mon": {
|
|
Daemons: map[string]proxmox.CephServiceDaemon{
|
|
"a": {Host: "node1", Status: "running"},
|
|
},
|
|
},
|
|
},
|
|
serviceType: "mon",
|
|
want: 1,
|
|
},
|
|
{
|
|
name: "multiple daemons",
|
|
services: map[string]proxmox.CephServiceDefinition{
|
|
"mon": {
|
|
Daemons: map[string]proxmox.CephServiceDaemon{
|
|
"a": {Host: "node1", Status: "running"},
|
|
"b": {Host: "node2", Status: "running"},
|
|
"c": {Host: "node3", Status: "running"},
|
|
},
|
|
},
|
|
},
|
|
serviceType: "mon",
|
|
want: 3,
|
|
},
|
|
{
|
|
name: "multiple service types",
|
|
services: map[string]proxmox.CephServiceDefinition{
|
|
"mon": {
|
|
Daemons: map[string]proxmox.CephServiceDaemon{
|
|
"a": {Host: "node1", Status: "running"},
|
|
"b": {Host: "node2", Status: "running"},
|
|
},
|
|
},
|
|
"mgr": {
|
|
Daemons: map[string]proxmox.CephServiceDaemon{
|
|
"node1": {Host: "node1", Status: "active"},
|
|
},
|
|
},
|
|
},
|
|
serviceType: "mgr",
|
|
want: 1,
|
|
},
|
|
{
|
|
name: "empty daemons map",
|
|
services: map[string]proxmox.CephServiceDefinition{
|
|
"mon": {
|
|
Daemons: map[string]proxmox.CephServiceDaemon{},
|
|
},
|
|
},
|
|
serviceType: "mon",
|
|
want: 0,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if got := countServiceDaemons(tc.services, tc.serviceType); got != tc.want {
|
|
t.Fatalf("countServiceDaemons(%v, %q) = %d, want %d", tc.services, tc.serviceType, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractCephCheckSummary(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
raw json.RawMessage
|
|
want string
|
|
}{
|
|
// Empty/nil cases
|
|
{
|
|
name: "nil raw message",
|
|
raw: nil,
|
|
want: "",
|
|
},
|
|
{
|
|
name: "empty raw message",
|
|
raw: json.RawMessage{},
|
|
want: "",
|
|
},
|
|
|
|
// Object format with message field
|
|
{
|
|
name: "object with message",
|
|
raw: json.RawMessage(`{"message": "1 slow ops"}`),
|
|
want: "1 slow ops",
|
|
},
|
|
{
|
|
name: "object with summary",
|
|
raw: json.RawMessage(`{"summary": "health warning"}`),
|
|
want: "health warning",
|
|
},
|
|
{
|
|
name: "object with both message and summary prefers message",
|
|
raw: json.RawMessage(`{"message": "from message", "summary": "from summary"}`),
|
|
want: "from message",
|
|
},
|
|
{
|
|
name: "object with empty message falls back to summary",
|
|
raw: json.RawMessage(`{"message": "", "summary": "fallback summary"}`),
|
|
want: "fallback summary",
|
|
},
|
|
|
|
// Array format
|
|
{
|
|
name: "array with single item message",
|
|
raw: json.RawMessage(`[{"message": "array message"}]`),
|
|
want: "array message",
|
|
},
|
|
{
|
|
name: "array with single item summary",
|
|
raw: json.RawMessage(`[{"summary": "array summary"}]`),
|
|
want: "array summary",
|
|
},
|
|
{
|
|
name: "array with multiple items returns first",
|
|
raw: json.RawMessage(`[{"message": "first"}, {"message": "second"}]`),
|
|
want: "first",
|
|
},
|
|
{
|
|
name: "array skips empty to find message",
|
|
raw: json.RawMessage(`[{"message": ""}, {"message": "second"}]`),
|
|
want: "second",
|
|
},
|
|
{
|
|
name: "empty array",
|
|
raw: json.RawMessage(`[]`),
|
|
want: "",
|
|
},
|
|
|
|
// Plain string format
|
|
{
|
|
name: "plain string",
|
|
raw: json.RawMessage(`"simple string message"`),
|
|
want: "simple string message",
|
|
},
|
|
{
|
|
name: "empty string",
|
|
raw: json.RawMessage(`""`),
|
|
want: "",
|
|
},
|
|
|
|
// Invalid JSON
|
|
{
|
|
name: "invalid JSON",
|
|
raw: json.RawMessage(`{invalid`),
|
|
want: "",
|
|
},
|
|
{
|
|
name: "number value",
|
|
raw: json.RawMessage(`123`),
|
|
want: "",
|
|
},
|
|
{
|
|
name: "boolean value",
|
|
raw: json.RawMessage(`true`),
|
|
want: "",
|
|
},
|
|
{
|
|
name: "null value",
|
|
raw: json.RawMessage(`null`),
|
|
want: "",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if got := extractCephCheckSummary(tc.raw); got != tc.want {
|
|
t.Fatalf("extractCephCheckSummary(%s) = %q, want %q", tc.raw, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSummarizeCephHealth(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
status *proxmox.CephStatus
|
|
want string
|
|
}{
|
|
{
|
|
name: "nil status",
|
|
status: nil,
|
|
want: "",
|
|
},
|
|
{
|
|
name: "empty status",
|
|
status: &proxmox.CephStatus{},
|
|
want: "",
|
|
},
|
|
{
|
|
name: "single summary message",
|
|
status: &proxmox.CephStatus{
|
|
Health: proxmox.CephHealth{
|
|
Summary: []proxmox.CephHealthSummary{
|
|
{Message: "HEALTH_WARN"},
|
|
},
|
|
},
|
|
},
|
|
want: "HEALTH_WARN",
|
|
},
|
|
{
|
|
name: "summary with summary field instead of message",
|
|
status: &proxmox.CephStatus{
|
|
Health: proxmox.CephHealth{
|
|
Summary: []proxmox.CephHealthSummary{
|
|
{Summary: "1 pool(s) have no replicas configured"},
|
|
},
|
|
},
|
|
},
|
|
want: "1 pool(s) have no replicas configured",
|
|
},
|
|
{
|
|
name: "multiple summary messages",
|
|
status: &proxmox.CephStatus{
|
|
Health: proxmox.CephHealth{
|
|
Summary: []proxmox.CephHealthSummary{
|
|
{Message: "first warning"},
|
|
{Message: "second warning"},
|
|
},
|
|
},
|
|
},
|
|
want: "first warning; second warning",
|
|
},
|
|
{
|
|
name: "health check with summary object",
|
|
status: &proxmox.CephStatus{
|
|
Health: proxmox.CephHealth{
|
|
Checks: map[string]proxmox.CephHealthCheckRaw{
|
|
"SLOW_OPS": {
|
|
Summary: json.RawMessage(`{"message": "1 slow ops"}`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: "SLOW_OPS: 1 slow ops",
|
|
},
|
|
{
|
|
name: "health check with detail fallback",
|
|
status: &proxmox.CephStatus{
|
|
Health: proxmox.CephHealth{
|
|
Checks: map[string]proxmox.CephHealthCheckRaw{
|
|
"OSD_DOWN": {
|
|
Summary: json.RawMessage(`{}`),
|
|
Detail: []proxmox.CephCheckDetail{
|
|
{Message: "osd.0 is down"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: "OSD_DOWN: osd.0 is down",
|
|
},
|
|
{
|
|
name: "combined summary and checks",
|
|
status: &proxmox.CephStatus{
|
|
Health: proxmox.CephHealth{
|
|
Summary: []proxmox.CephHealthSummary{
|
|
{Message: "HEALTH_WARN"},
|
|
},
|
|
Checks: map[string]proxmox.CephHealthCheckRaw{
|
|
"PG_DEGRADED": {
|
|
Summary: json.RawMessage(`{"message": "Degraded data redundancy"}`),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
want: "HEALTH_WARN; PG_DEGRADED: Degraded data redundancy",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if got := summarizeCephHealth(tc.status); got != tc.want {
|
|
t.Fatalf("summarizeCephHealth() = %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|