mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 00:17:39 +01:00
- Add reporting engine for scheduled and on-demand reports - Implement CSV export functionality - Implement PDF report generation
346 lines
7.8 KiB
Go
346 lines
7.8 KiB
Go
package reporting
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestCSVGenerator_Generate(t *testing.T) {
|
|
data := createTestReportData()
|
|
|
|
gen := NewCSVGenerator()
|
|
result, err := gen.Generate(data)
|
|
if err != nil {
|
|
t.Fatalf("CSV generation failed: %v", err)
|
|
}
|
|
|
|
csv := string(result)
|
|
|
|
// Check header
|
|
if !strings.Contains(csv, "# Pulse Metrics Report") {
|
|
t.Error("Missing report header")
|
|
}
|
|
if !strings.Contains(csv, "Test Report") {
|
|
t.Error("Missing title")
|
|
}
|
|
if !strings.Contains(csv, "node") {
|
|
t.Error("Missing resource type")
|
|
}
|
|
if !strings.Contains(csv, "test-node-1") {
|
|
t.Error("Missing resource ID")
|
|
}
|
|
|
|
// Check summary section
|
|
if !strings.Contains(csv, "# SUMMARY") {
|
|
t.Error("Missing summary section")
|
|
}
|
|
if !strings.Contains(csv, "CPU Usage") {
|
|
t.Error("Missing CPU metric in summary")
|
|
}
|
|
|
|
// Check data section
|
|
if !strings.Contains(csv, "# DATA") {
|
|
t.Error("Missing data section")
|
|
}
|
|
if !strings.Contains(csv, "Timestamp") {
|
|
t.Error("Missing timestamp column header")
|
|
}
|
|
}
|
|
|
|
func TestPDFGenerator_Generate(t *testing.T) {
|
|
data := createTestReportData()
|
|
|
|
gen := NewPDFGenerator()
|
|
result, err := gen.Generate(data)
|
|
if err != nil {
|
|
t.Fatalf("PDF generation failed: %v", err)
|
|
}
|
|
|
|
// Check PDF magic bytes
|
|
if len(result) < 4 {
|
|
t.Fatal("PDF too short")
|
|
}
|
|
if string(result[:4]) != "%PDF" {
|
|
t.Error("Missing PDF magic bytes")
|
|
}
|
|
|
|
// Check reasonable size (should be at least a few KB)
|
|
if len(result) < 1000 {
|
|
t.Errorf("PDF seems too small: %d bytes", len(result))
|
|
}
|
|
}
|
|
|
|
func TestPDFGenerator_EmptyData(t *testing.T) {
|
|
data := &ReportData{
|
|
Title: "Empty Report",
|
|
ResourceType: "node",
|
|
ResourceID: "empty-node",
|
|
Start: time.Now().Add(-1 * time.Hour),
|
|
End: time.Now(),
|
|
GeneratedAt: time.Now(),
|
|
Metrics: make(map[string][]MetricDataPoint),
|
|
Summary: MetricSummary{
|
|
ByMetric: make(map[string]MetricStats),
|
|
},
|
|
}
|
|
|
|
gen := NewPDFGenerator()
|
|
result, err := gen.Generate(data)
|
|
if err != nil {
|
|
t.Fatalf("PDF generation failed for empty data: %v", err)
|
|
}
|
|
|
|
if string(result[:4]) != "%PDF" {
|
|
t.Error("Missing PDF magic bytes for empty report")
|
|
}
|
|
}
|
|
|
|
func TestCSVGenerator_EmptyData(t *testing.T) {
|
|
data := &ReportData{
|
|
Title: "Empty Report",
|
|
ResourceType: "node",
|
|
ResourceID: "empty-node",
|
|
Start: time.Now().Add(-1 * time.Hour),
|
|
End: time.Now(),
|
|
GeneratedAt: time.Now(),
|
|
Metrics: make(map[string][]MetricDataPoint),
|
|
Summary: MetricSummary{
|
|
ByMetric: make(map[string]MetricStats),
|
|
},
|
|
}
|
|
|
|
gen := NewCSVGenerator()
|
|
result, err := gen.Generate(data)
|
|
if err != nil {
|
|
t.Fatalf("CSV generation failed for empty data: %v", err)
|
|
}
|
|
|
|
csv := string(result)
|
|
if !strings.Contains(csv, "# Pulse Metrics Report") {
|
|
t.Error("Missing header in empty report")
|
|
}
|
|
}
|
|
|
|
func TestGetResourceTypeDisplayName(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"node", "Node"},
|
|
{"vm", "Virtual Machine"},
|
|
{"container", "LXC Container"},
|
|
{"dockerHost", "Docker Host"},
|
|
{"dockerContainer", "Docker Container"},
|
|
{"storage", "Storage"},
|
|
{"unknown", "unknown"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
result := GetResourceTypeDisplayName(tc.input)
|
|
if result != tc.expected {
|
|
t.Errorf("GetResourceTypeDisplayName(%q) = %q, want %q", tc.input, result, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetMetricTypeDisplayName(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"cpu", "CPU Usage"},
|
|
{"memory", "Memory Usage"},
|
|
{"disk", "Disk Usage"},
|
|
{"usage", "Storage Usage"},
|
|
{"used", "Used Space"},
|
|
{"total", "Total Space"},
|
|
{"avail", "Available Space"},
|
|
{"unknown", "unknown"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
result := GetMetricTypeDisplayName(tc.input)
|
|
if result != tc.expected {
|
|
t.Errorf("GetMetricTypeDisplayName(%q) = %q, want %q", tc.input, result, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetMetricUnit(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"cpu", "%"},
|
|
{"memory", "%"},
|
|
{"disk", "%"},
|
|
{"usage", "%"},
|
|
{"used", "bytes"},
|
|
{"total", "bytes"},
|
|
{"avail", "bytes"},
|
|
{"unknown", ""},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
result := GetMetricUnit(tc.input)
|
|
if result != tc.expected {
|
|
t.Errorf("GetMetricUnit(%q) = %q, want %q", tc.input, result, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatBytes(t *testing.T) {
|
|
tests := []struct {
|
|
input float64
|
|
expected string
|
|
}{
|
|
{0, "0 B"},
|
|
{512, "512 B"},
|
|
{1024, "1.00 KiB"},
|
|
{1536, "1.50 KiB"},
|
|
{1048576, "1.00 MiB"},
|
|
{1073741824, "1.00 GiB"},
|
|
{1099511627776, "1.00 TiB"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
result := formatBytes(tc.input)
|
|
if result != tc.expected {
|
|
t.Errorf("formatBytes(%f) = %q, want %q", tc.input, result, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCSVGenerator_MultipleMetrics(t *testing.T) {
|
|
now := time.Now()
|
|
data := &ReportData{
|
|
Title: "Multi-Metric Test",
|
|
ResourceType: "node",
|
|
ResourceID: "node-1",
|
|
Start: now.Add(-1 * time.Hour),
|
|
End: now,
|
|
GeneratedAt: now,
|
|
Metrics: map[string][]MetricDataPoint{
|
|
"cpu": {
|
|
{Timestamp: now.Add(-30 * time.Minute), Value: 50.0},
|
|
{Timestamp: now, Value: 60.0},
|
|
},
|
|
"memory": {
|
|
{Timestamp: now.Add(-30 * time.Minute), Value: 70.0},
|
|
{Timestamp: now, Value: 75.0},
|
|
},
|
|
"disk": {
|
|
{Timestamp: now.Add(-30 * time.Minute), Value: 40.0},
|
|
{Timestamp: now, Value: 42.0},
|
|
},
|
|
},
|
|
Summary: MetricSummary{
|
|
ByMetric: map[string]MetricStats{
|
|
"cpu": {MetricType: "cpu", Count: 2, Min: 50, Max: 60, Avg: 55, Current: 60},
|
|
"memory": {MetricType: "memory", Count: 2, Min: 70, Max: 75, Avg: 72.5, Current: 75},
|
|
"disk": {MetricType: "disk", Count: 2, Min: 40, Max: 42, Avg: 41, Current: 42},
|
|
},
|
|
},
|
|
TotalPoints: 6,
|
|
}
|
|
|
|
gen := NewCSVGenerator()
|
|
result, err := gen.Generate(data)
|
|
if err != nil {
|
|
t.Fatalf("CSV generation failed: %v", err)
|
|
}
|
|
|
|
csv := string(result)
|
|
|
|
// Check all metrics are present in summary
|
|
if !strings.Contains(csv, "CPU Usage") {
|
|
t.Error("Missing CPU in summary")
|
|
}
|
|
if !strings.Contains(csv, "Memory Usage") {
|
|
t.Error("Missing Memory in summary")
|
|
}
|
|
if !strings.Contains(csv, "Disk Usage") {
|
|
t.Error("Missing Disk in summary")
|
|
}
|
|
|
|
// Check data rows
|
|
lines := strings.Split(csv, "\n")
|
|
dataStarted := false
|
|
dataRows := 0
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(line, "# DATA") {
|
|
dataStarted = true
|
|
continue
|
|
}
|
|
if dataStarted && !strings.HasPrefix(line, "#") && !strings.HasPrefix(line, "Timestamp") && line != "" {
|
|
dataRows++
|
|
}
|
|
}
|
|
|
|
// Should have data rows (timestamps may be merged or separate)
|
|
if dataRows == 0 {
|
|
t.Error("No data rows in CSV")
|
|
}
|
|
}
|
|
|
|
// createTestReportData creates sample report data for testing.
|
|
func createTestReportData() *ReportData {
|
|
now := time.Now()
|
|
start := now.Add(-1 * time.Hour)
|
|
|
|
// Create sample data points
|
|
cpuPoints := make([]MetricDataPoint, 12)
|
|
memPoints := make([]MetricDataPoint, 12)
|
|
|
|
for i := 0; i < 12; i++ {
|
|
ts := start.Add(time.Duration(i*5) * time.Minute)
|
|
cpuPoints[i] = MetricDataPoint{
|
|
Timestamp: ts,
|
|
Value: float64(50 + i*2),
|
|
Min: float64(48 + i*2),
|
|
Max: float64(52 + i*2),
|
|
}
|
|
memPoints[i] = MetricDataPoint{
|
|
Timestamp: ts,
|
|
Value: float64(60 + i),
|
|
Min: float64(58 + i),
|
|
Max: float64(62 + i),
|
|
}
|
|
}
|
|
|
|
return &ReportData{
|
|
Title: "Test Report",
|
|
ResourceType: "node",
|
|
ResourceID: "test-node-1",
|
|
Start: start,
|
|
End: now,
|
|
GeneratedAt: now,
|
|
Metrics: map[string][]MetricDataPoint{
|
|
"cpu": cpuPoints,
|
|
"memory": memPoints,
|
|
},
|
|
Summary: MetricSummary{
|
|
ByMetric: map[string]MetricStats{
|
|
"cpu": {
|
|
MetricType: "cpu",
|
|
Count: 12,
|
|
Min: 50,
|
|
Max: 72,
|
|
Avg: 61,
|
|
Current: 72,
|
|
},
|
|
"memory": {
|
|
MetricType: "memory",
|
|
Count: 12,
|
|
Min: 60,
|
|
Max: 71,
|
|
Avg: 65.5,
|
|
Current: 71,
|
|
},
|
|
},
|
|
},
|
|
TotalPoints: 24,
|
|
}
|
|
}
|