mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-29 12:00:13 +00:00
- analyze_coverage.py (local coverage analysis script) - coverage_summary.txt (coverage output) - mock.env (environment file)
432 lines
14 KiB
Go
432 lines
14 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
"github.com/stretchr/testify/mock"
|
|
)
|
|
|
|
type stubBaselineProvider struct {
|
|
baselines map[string]map[string]*MetricBaseline
|
|
}
|
|
|
|
func (s *stubBaselineProvider) GetBaseline(resourceID, metric string) *MetricBaseline {
|
|
if s.baselines == nil {
|
|
return nil
|
|
}
|
|
if metrics, ok := s.baselines[resourceID]; ok {
|
|
return metrics[metric]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *stubBaselineProvider) GetAllBaselines() map[string]map[string]*MetricBaseline {
|
|
return s.baselines
|
|
}
|
|
|
|
type stubPatternProvider struct {
|
|
patterns []Pattern
|
|
predictions []Prediction
|
|
}
|
|
|
|
func (s *stubPatternProvider) GetPatterns() []Pattern {
|
|
return s.patterns
|
|
}
|
|
|
|
func (s *stubPatternProvider) GetPredictions() []Prediction {
|
|
return s.predictions
|
|
}
|
|
|
|
type stubFindingsManager struct {
|
|
resolveErr error
|
|
dismissErr error
|
|
}
|
|
|
|
func (s *stubFindingsManager) ResolveFinding(string, string) error {
|
|
return s.resolveErr
|
|
}
|
|
|
|
func (s *stubFindingsManager) DismissFinding(string, string, string) error {
|
|
return s.dismissErr
|
|
}
|
|
|
|
func TestExecuteGetMetrics(t *testing.T) {
|
|
executor := NewPulseToolExecutor(ExecutorConfig{StateProvider: &mockStateProvider{}})
|
|
result, _ := executor.executeGetMetrics(context.Background(), map[string]interface{}{"period": "24h"})
|
|
if result.IsError || result.Content[0].Text == "" {
|
|
t.Fatal("expected metrics not available message")
|
|
}
|
|
|
|
metricsProv := &mockMetricsHistoryProvider{}
|
|
metricsProv.On("GetAllMetricsSummary", mock.Anything).Return(map[string]ResourceMetricsSummary{
|
|
"res1": {ResourceID: "res1"},
|
|
}, nil)
|
|
metricsProv.On("GetResourceMetrics", "res1", mock.Anything).Return([]MetricPoint{
|
|
{CPU: 1, Memory: 2},
|
|
}, nil)
|
|
executor.metricsHistory = metricsProv
|
|
result, _ = executor.executeGetMetrics(context.Background(), map[string]interface{}{
|
|
"period": "bad",
|
|
"resource_id": "res1",
|
|
})
|
|
var resp MetricsResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &resp); err != nil {
|
|
t.Fatalf("decode metrics response: %v", err)
|
|
}
|
|
if resp.ResourceID != "res1" || len(resp.Points) != 1 {
|
|
t.Fatalf("unexpected metrics response: %+v", resp)
|
|
}
|
|
|
|
result, _ = executor.executeGetMetrics(context.Background(), map[string]interface{}{
|
|
"period": "7d",
|
|
})
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &resp); err != nil {
|
|
t.Fatalf("decode metrics response: %v", err)
|
|
}
|
|
if resp.Summary == nil || resp.Period != "7d" {
|
|
t.Fatalf("unexpected metrics summary: %+v", resp)
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetBaselinesAndPatterns(t *testing.T) {
|
|
executor := NewPulseToolExecutor(ExecutorConfig{StateProvider: &mockStateProvider{}})
|
|
result, _ := executor.executeGetBaselines(context.Background(), map[string]interface{}{})
|
|
if result.IsError {
|
|
t.Fatal("expected baselines not available message")
|
|
}
|
|
|
|
executor.baselineProvider = &stubBaselineProvider{
|
|
baselines: map[string]map[string]*MetricBaseline{
|
|
"res1": {"cpu": {Mean: 1}},
|
|
},
|
|
}
|
|
result, _ = executor.executeGetBaselines(context.Background(), map[string]interface{}{
|
|
"resource_id": "res1",
|
|
})
|
|
var baselines BaselinesResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &baselines); err != nil {
|
|
t.Fatalf("decode baselines: %v", err)
|
|
}
|
|
if baselines.ResourceID != "res1" || baselines.Baselines["res1"]["cpu"].Mean != 1 {
|
|
t.Fatalf("unexpected baselines: %+v", baselines)
|
|
}
|
|
|
|
result, _ = executor.executeGetPatterns(context.Background(), map[string]interface{}{})
|
|
if result.IsError {
|
|
t.Fatal("expected patterns not available message")
|
|
}
|
|
|
|
executor.patternProvider = &stubPatternProvider{
|
|
patterns: []Pattern{{ResourceID: "r1"}},
|
|
predictions: []Prediction{{ResourceID: "r2"}},
|
|
}
|
|
result, _ = executor.executeGetPatterns(context.Background(), map[string]interface{}{})
|
|
var patterns PatternsResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &patterns); err != nil {
|
|
t.Fatalf("decode patterns: %v", err)
|
|
}
|
|
if len(patterns.Patterns) != 1 || len(patterns.Predictions) != 1 {
|
|
t.Fatalf("unexpected patterns: %+v", patterns)
|
|
}
|
|
}
|
|
|
|
func TestExecuteListResolvedAlerts(t *testing.T) {
|
|
executor := NewPulseToolExecutor(ExecutorConfig{})
|
|
result, _ := executor.executeListResolvedAlerts(context.Background(), map[string]interface{}{})
|
|
if result.Content[0].Text != "State provider not available." {
|
|
t.Fatalf("unexpected response: %s", result.Content[0].Text)
|
|
}
|
|
|
|
executor.stateProvider = &mockStateProvider{state: models.StateSnapshot{}}
|
|
result, _ = executor.executeListResolvedAlerts(context.Background(), map[string]interface{}{})
|
|
if result.Content[0].Text != "No recently resolved alerts." {
|
|
t.Fatalf("unexpected response: %s", result.Content[0].Text)
|
|
}
|
|
|
|
now := time.Now()
|
|
executor.stateProvider = &mockStateProvider{state: models.StateSnapshot{
|
|
RecentlyResolved: []models.ResolvedAlert{
|
|
{
|
|
Alert: models.Alert{
|
|
ID: "a1",
|
|
Type: "cpu",
|
|
Level: "warning",
|
|
ResourceID: "r1",
|
|
ResourceName: "node1",
|
|
Node: "node1",
|
|
Instance: "i1",
|
|
Message: "msg",
|
|
Value: 1,
|
|
Threshold: 2,
|
|
StartTime: now,
|
|
},
|
|
ResolvedTime: now,
|
|
},
|
|
{
|
|
Alert: models.Alert{
|
|
ID: "a2",
|
|
Type: "disk",
|
|
Level: "critical",
|
|
ResourceID: "r2",
|
|
ResourceName: "node2",
|
|
Node: "node2",
|
|
Instance: "i2",
|
|
Message: "msg2",
|
|
Value: 3,
|
|
Threshold: 4,
|
|
StartTime: now,
|
|
},
|
|
ResolvedTime: now,
|
|
},
|
|
},
|
|
}}
|
|
|
|
result, _ = executor.executeListResolvedAlerts(context.Background(), map[string]interface{}{
|
|
"type": "cpu",
|
|
"level": "warning",
|
|
"limit": 1,
|
|
})
|
|
|
|
var resp ResolvedAlertsResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &resp); err != nil {
|
|
t.Fatalf("decode resolved alerts: %v", err)
|
|
}
|
|
if len(resp.Alerts) != 1 || resp.Alerts[0].ID != "a1" {
|
|
t.Fatalf("unexpected resolved alerts: %+v", resp)
|
|
}
|
|
}
|
|
|
|
func TestExecuteListAlertsAndFindings(t *testing.T) {
|
|
executor := NewPulseToolExecutor(ExecutorConfig{StateProvider: &mockStateProvider{}})
|
|
result, _ := executor.executeListAlerts(context.Background(), map[string]interface{}{})
|
|
if result.IsError {
|
|
t.Fatal("expected alerts not available message")
|
|
}
|
|
|
|
alertProv := &mockAlertProvider{}
|
|
alertProv.On("GetActiveAlerts").Return([]ActiveAlert{
|
|
{ID: "a1", Severity: "warning"},
|
|
{ID: "a2", Severity: "critical"},
|
|
})
|
|
executor.alertProvider = alertProv
|
|
result, _ = executor.executeListAlerts(context.Background(), map[string]interface{}{
|
|
"severity": "critical",
|
|
"limit": float64(1),
|
|
})
|
|
var alerts AlertsResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &alerts); err != nil {
|
|
t.Fatalf("decode alerts: %v", err)
|
|
}
|
|
if alerts.Count != 1 || alerts.Alerts[0].ID != "a2" {
|
|
t.Fatalf("unexpected alerts: %+v", alerts)
|
|
}
|
|
|
|
findingsProv := &mockFindingsProvider{}
|
|
findingsProv.On("GetActiveFindings").Return([]Finding{{ID: "f1", Severity: "warning"}})
|
|
findingsProv.On("GetDismissedFindings").Return([]Finding{{ID: "f2", Severity: "info"}})
|
|
executor.findingsProvider = findingsProv
|
|
result, _ = executor.executeListFindings(context.Background(), map[string]interface{}{
|
|
"include_dismissed": true,
|
|
"severity": "warning",
|
|
})
|
|
var findings FindingsResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &findings); err != nil {
|
|
t.Fatalf("decode findings: %v", err)
|
|
}
|
|
if findings.Counts.Active != 1 || findings.Counts.Dismissed != 0 {
|
|
t.Fatalf("unexpected counts: %+v", findings.Counts)
|
|
}
|
|
if len(findings.Active) != 1 || len(findings.Dismissed) != 0 {
|
|
t.Fatalf("unexpected findings: %+v", findings)
|
|
}
|
|
}
|
|
|
|
func TestExecuteResolveAndDismissFinding(t *testing.T) {
|
|
executor := NewPulseToolExecutor(ExecutorConfig{StateProvider: &mockStateProvider{}})
|
|
result, _ := executor.executeResolveFinding(context.Background(), map[string]interface{}{})
|
|
if !result.IsError {
|
|
t.Fatal("expected error without findings manager")
|
|
}
|
|
|
|
executor.findingsManager = &stubFindingsManager{resolveErr: errors.New("resolve")}
|
|
result, _ = executor.executeResolveFinding(context.Background(), map[string]interface{}{
|
|
"finding_id": "f1",
|
|
"resolution_note": "note",
|
|
})
|
|
if !result.IsError {
|
|
t.Fatal("expected resolve error")
|
|
}
|
|
|
|
executor.findingsManager = &stubFindingsManager{dismissErr: errors.New("dismiss")}
|
|
result, _ = executor.executeDismissFinding(context.Background(), map[string]interface{}{
|
|
"finding_id": "f1",
|
|
"reason": "not_an_issue",
|
|
"note": "note",
|
|
})
|
|
if !result.IsError {
|
|
t.Fatal("expected dismiss error")
|
|
}
|
|
|
|
executor.findingsManager = &stubFindingsManager{}
|
|
result, _ = executor.executeDismissFinding(context.Background(), map[string]interface{}{
|
|
"finding_id": "f1",
|
|
"reason": "not_an_issue",
|
|
"note": "note",
|
|
})
|
|
var okResp map[string]interface{}
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &okResp); err != nil {
|
|
t.Fatalf("decode dismiss response: %v", err)
|
|
}
|
|
if okResp["success"] != true {
|
|
t.Fatalf("unexpected dismiss response: %+v", okResp)
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetMetrics_InvalidResourceType(t *testing.T) {
|
|
executor := NewPulseToolExecutor(ExecutorConfig{})
|
|
result, _ := executor.executeGetMetrics(context.Background(), map[string]interface{}{
|
|
"period": "24h",
|
|
"resource_type": "bad",
|
|
})
|
|
if !result.IsError {
|
|
t.Fatal("expected error for invalid resource_type")
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetMetrics_FilterAndPagination(t *testing.T) {
|
|
metricsProv := &mockMetricsHistoryProvider{}
|
|
metricsProv.On("GetAllMetricsSummary", mock.Anything).Return(map[string]ResourceMetricsSummary{
|
|
"vm1": {ResourceID: "vm1", ResourceType: "vm"},
|
|
"vm2": {ResourceID: "vm2", ResourceType: "vm"},
|
|
"ct1": {ResourceID: "ct1", ResourceType: "container"},
|
|
}, nil)
|
|
|
|
executor := NewPulseToolExecutor(ExecutorConfig{
|
|
StateProvider: &mockStateProvider{},
|
|
MetricsHistory: metricsProv,
|
|
})
|
|
|
|
result, _ := executor.executeGetMetrics(context.Background(), map[string]interface{}{
|
|
"period": "24h",
|
|
"resource_type": "vm",
|
|
"limit": 1,
|
|
"offset": 1,
|
|
})
|
|
var resp MetricsResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &resp); err != nil {
|
|
t.Fatalf("decode metrics response: %v", err)
|
|
}
|
|
if len(resp.Summary) != 1 {
|
|
t.Fatalf("unexpected summary: %+v", resp.Summary)
|
|
}
|
|
if resp.Pagination == nil || resp.Pagination.Total != 2 || resp.Pagination.Offset != 1 {
|
|
t.Fatalf("unexpected pagination: %+v", resp.Pagination)
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetBaselines_FilterAndErrors(t *testing.T) {
|
|
executor := NewPulseToolExecutor(ExecutorConfig{})
|
|
result, _ := executor.executeGetBaselines(context.Background(), map[string]interface{}{
|
|
"resource_type": "bad",
|
|
})
|
|
if !result.IsError {
|
|
t.Fatal("expected error for invalid resource_type")
|
|
}
|
|
|
|
executor = NewPulseToolExecutor(ExecutorConfig{
|
|
BaselineProvider: &stubBaselineProvider{
|
|
baselines: map[string]map[string]*MetricBaseline{"100": {"cpu": {Mean: 1}}},
|
|
},
|
|
})
|
|
result, _ = executor.executeGetBaselines(context.Background(), map[string]interface{}{
|
|
"resource_type": "vm",
|
|
})
|
|
if !result.IsError {
|
|
t.Fatal("expected error without state provider")
|
|
}
|
|
|
|
executor = NewPulseToolExecutor(ExecutorConfig{
|
|
StateProvider: &mockStateProvider{state: models.StateSnapshot{
|
|
VMs: []models.VM{{VMID: 100}, {VMID: 101}},
|
|
}},
|
|
BaselineProvider: &stubBaselineProvider{
|
|
baselines: map[string]map[string]*MetricBaseline{
|
|
"100": {"cpu": {Mean: 1}},
|
|
"101": {"cpu": {Mean: 2}},
|
|
},
|
|
},
|
|
})
|
|
result, _ = executor.executeGetBaselines(context.Background(), map[string]interface{}{
|
|
"resource_type": "vm",
|
|
"limit": 1,
|
|
"offset": 1,
|
|
})
|
|
var baselines BaselinesResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &baselines); err != nil {
|
|
t.Fatalf("decode baselines: %v", err)
|
|
}
|
|
if len(baselines.Baselines) != 1 {
|
|
t.Fatalf("unexpected baselines: %+v", baselines.Baselines)
|
|
}
|
|
if baselines.Pagination == nil || baselines.Pagination.Total != 2 || baselines.Pagination.Offset != 1 {
|
|
t.Fatalf("unexpected pagination: %+v", baselines.Pagination)
|
|
}
|
|
}
|
|
|
|
func TestExecuteGetPatterns_EmptySlices(t *testing.T) {
|
|
executor := NewPulseToolExecutor(ExecutorConfig{})
|
|
executor.patternProvider = &stubPatternProvider{}
|
|
result, _ := executor.executeGetPatterns(context.Background(), map[string]interface{}{})
|
|
var resp PatternsResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &resp); err != nil {
|
|
t.Fatalf("decode patterns: %v", err)
|
|
}
|
|
if resp.Patterns == nil || resp.Predictions == nil {
|
|
t.Fatal("expected non-nil slices")
|
|
}
|
|
if len(resp.Patterns) != 0 || len(resp.Predictions) != 0 {
|
|
t.Fatalf("unexpected patterns: %+v", resp)
|
|
}
|
|
}
|
|
|
|
func TestExecuteListFindings_ResourceTypeFilter(t *testing.T) {
|
|
executor := NewPulseToolExecutor(ExecutorConfig{})
|
|
result, _ := executor.executeListFindings(context.Background(), map[string]interface{}{
|
|
"resource_type": "bad",
|
|
})
|
|
if !result.IsError {
|
|
t.Fatal("expected error for invalid resource_type")
|
|
}
|
|
|
|
findingsProv := &mockFindingsProvider{}
|
|
findingsProv.On("GetActiveFindings").Return([]Finding{
|
|
{ID: "f1", Severity: "warning", ResourceType: "docker container", ResourceID: "r1"},
|
|
{ID: "f2", Severity: "warning", ResourceType: "lxc container", ResourceID: "r2"},
|
|
})
|
|
findingsProv.On("GetDismissedFindings").Return([]Finding{
|
|
{ID: "f3", Severity: "warning", ResourceType: "docker-container", ResourceID: "r3"},
|
|
})
|
|
|
|
executor.findingsProvider = findingsProv
|
|
result, _ = executor.executeListFindings(context.Background(), map[string]interface{}{
|
|
"resource_type": "docker",
|
|
"include_dismissed": true,
|
|
"limit": 1,
|
|
"offset": 1,
|
|
})
|
|
var resp FindingsResponse
|
|
if err := json.Unmarshal([]byte(result.Content[0].Text), &resp); err != nil {
|
|
t.Fatalf("decode findings: %v", err)
|
|
}
|
|
if resp.Counts.Active != 1 || resp.Counts.Dismissed != 1 {
|
|
t.Fatalf("unexpected counts: %+v", resp.Counts)
|
|
}
|
|
if resp.Pagination == nil || resp.Pagination.Offset != 1 {
|
|
t.Fatalf("unexpected pagination: %+v", resp.Pagination)
|
|
}
|
|
}
|