mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-13 06:56:06 +00:00
Test coverage for isCephStorageType, countServiceDaemons, extractCephCheckSummary, and summarizeCephHealth functions. These parse Ceph storage types and health status JSON.
383 lines
8.5 KiB
Go
383 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)
|
|
}
|
|
})
|
|
}
|
|
}
|