mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 00:17:39 +01:00
Test: expand api feature test coverage
Add tests for AI intelligence, Docker/K8s agents, log redaction, and general router helper functions.
This commit is contained in:
212
internal/api/ai_intelligence_handlers_more_test.go
Normal file
212
internal/api/ai_intelligence_handlers_more_test.go
Normal file
@@ -0,0 +1,212 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/ai"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/ai/learning"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/ai/unified"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
)
|
||||
|
||||
type snapshotStateProvider struct {
|
||||
state models.StateSnapshot
|
||||
}
|
||||
|
||||
func (s snapshotStateProvider) GetState() models.StateSnapshot {
|
||||
return s.state
|
||||
}
|
||||
|
||||
func buildBaselineStore(t *testing.T) *ai.BaselineStore {
|
||||
t.Helper()
|
||||
store := ai.NewBaselineStore(ai.BaselineConfig{MinSamples: 1})
|
||||
points := []ai.BaselineMetricPoint{{Value: 10, Timestamp: time.Now()}}
|
||||
if err := store.Learn("vm-1", "vm", "cpu", points); err != nil {
|
||||
t.Fatalf("baseline Learn error: %v", err)
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func TestHandleGetRecentChanges_WithDetector(t *testing.T) {
|
||||
svc := newEnabledAIService(t)
|
||||
detector := ai.NewChangeDetector(ai.ChangeDetectorConfig{MaxChanges: 10})
|
||||
change := ai.Change{
|
||||
ID: "change-1",
|
||||
ResourceID: "vm-1",
|
||||
ResourceName: "vm-one",
|
||||
ResourceType: "vm",
|
||||
ChangeType: ai.ChangeConfig,
|
||||
Before: "old",
|
||||
After: "new",
|
||||
DetectedAt: time.Now().Add(-30 * time.Minute),
|
||||
Description: "updated config",
|
||||
}
|
||||
setUnexportedField(t, detector, "changes", []ai.Change{change})
|
||||
svc.SetChangeDetector(detector)
|
||||
|
||||
handler := &AISettingsHandler{legacyAIService: svc}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/changes?hours=1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.HandleGetRecentChanges(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["count"] != float64(1) {
|
||||
t.Fatalf("expected count 1, got %#v", payload["count"])
|
||||
}
|
||||
changes, _ := payload["changes"].([]interface{})
|
||||
if len(changes) != 1 {
|
||||
t.Fatalf("expected 1 change, got %d", len(changes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetBaselines_WithStore(t *testing.T) {
|
||||
svc := newEnabledAIService(t)
|
||||
store := buildBaselineStore(t)
|
||||
svc.SetBaselineStore(store)
|
||||
|
||||
handler := &AISettingsHandler{legacyAIService: svc}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/baselines?resource_id=vm-1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.HandleGetBaselines(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["count"] != float64(1) {
|
||||
t.Fatalf("expected count 1, got %#v", payload["count"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetLearningStatus_WithBaselines(t *testing.T) {
|
||||
svc := newEnabledAIService(t)
|
||||
store := buildBaselineStore(t)
|
||||
svc.SetBaselineStore(store)
|
||||
|
||||
handler := &AISettingsHandler{legacyAIService: svc}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/learning", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.HandleGetLearningStatus(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["resources_baselined"] != float64(1) {
|
||||
t.Fatalf("expected resources_baselined 1, got %#v", payload["resources_baselined"])
|
||||
}
|
||||
if payload["status"] != "learning" {
|
||||
t.Fatalf("expected status learning, got %#v", payload["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetAnomalies_WithBaseline(t *testing.T) {
|
||||
svc := newEnabledAIService(t)
|
||||
store := buildBaselineStore(t)
|
||||
svc.SetBaselineStore(store)
|
||||
|
||||
state := models.StateSnapshot{
|
||||
VMs: []models.VM{{
|
||||
ID: "vm-1",
|
||||
Name: "vm-one",
|
||||
Status: "running",
|
||||
CPU: 0.8,
|
||||
Memory: models.Memory{Usage: 50},
|
||||
}},
|
||||
}
|
||||
svc.SetStateProvider(snapshotStateProvider{state: state})
|
||||
|
||||
handler := &AISettingsHandler{legacyAIService: svc}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/anomalies?resource_id=vm-1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.HandleGetAnomalies(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["count"] == float64(0) {
|
||||
t.Fatalf("expected anomalies to be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetLearningPreferences_WithStore(t *testing.T) {
|
||||
store := learning.NewLearningStore(learning.LearningStoreConfig{})
|
||||
handler := &AISettingsHandler{}
|
||||
handler.SetLearningStore(store)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/learning/preferences?resource_id=vm-1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.HandleGetLearningPreferences(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["resource_id"] != "vm-1" {
|
||||
t.Fatalf("expected resource_id in response, got %#v", payload["resource_id"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetUnifiedFindings_WithStore(t *testing.T) {
|
||||
store := unified.NewUnifiedStore(unified.DefaultAlertToFindingConfig())
|
||||
store.AddFromAI(&unified.UnifiedFinding{
|
||||
ID: "finding-1",
|
||||
Source: unified.SourceAIPatrol,
|
||||
Severity: unified.SeverityCritical,
|
||||
Category: unified.CategoryPerformance,
|
||||
ResourceID: "vm-1",
|
||||
ResourceName: "vm-one",
|
||||
ResourceType: "vm",
|
||||
Title: "CPU high",
|
||||
Description: "cpu usage high",
|
||||
DetectedAt: time.Now(),
|
||||
LastSeenAt: time.Now(),
|
||||
})
|
||||
|
||||
handler := &AISettingsHandler{}
|
||||
handler.SetUnifiedStore(store)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/unified/findings?resource_id=vm-1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.HandleGetUnifiedFindings(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["count"] == float64(0) {
|
||||
t.Fatalf("expected findings in response")
|
||||
}
|
||||
}
|
||||
213
internal/api/ai_intelligence_handlers_remediation_more_test.go
Normal file
213
internal/api/ai_intelligence_handlers_remediation_more_test.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/ai"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/ai/remediation"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/license"
|
||||
)
|
||||
|
||||
func TestHandleGetRemediations_WithLog(t *testing.T) {
|
||||
svc := newEnabledAIService(t)
|
||||
log := ai.NewRemediationLog(ai.RemediationLogConfig{MaxRecords: 10})
|
||||
record := ai.RemediationRecord{
|
||||
ResourceID: "vm-1",
|
||||
ResourceType: "vm",
|
||||
ResourceName: "vm-one",
|
||||
FindingID: "finding-1",
|
||||
Problem: "cpu high",
|
||||
Action: "restart",
|
||||
Outcome: ai.OutcomeResolved,
|
||||
Automatic: true,
|
||||
Timestamp: time.Now(),
|
||||
Duration: 2 * time.Second,
|
||||
}
|
||||
if err := log.Log(record); err != nil {
|
||||
t.Fatalf("log remediation: %v", err)
|
||||
}
|
||||
|
||||
patrol := svc.GetPatrolService()
|
||||
setUnexportedField(t, patrol, "remediationLog", log)
|
||||
|
||||
handler := &AISettingsHandler{legacyAIService: svc}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/remediations?hours=1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.HandleGetRemediations(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["count"] != float64(1) {
|
||||
t.Fatalf("expected count 1, got %#v", payload["count"])
|
||||
}
|
||||
stats := payload["stats"].(map[string]interface{})
|
||||
if stats["resolved"] != float64(1) || stats["automatic"] != float64(1) {
|
||||
t.Fatalf("unexpected stats: %#v", stats)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetRemediations_FilterFinding(t *testing.T) {
|
||||
svc := newEnabledAIService(t)
|
||||
log := ai.NewRemediationLog(ai.RemediationLogConfig{MaxRecords: 10})
|
||||
record := ai.RemediationRecord{
|
||||
ResourceID: "vm-2",
|
||||
ResourceType: "vm",
|
||||
ResourceName: "vm-two",
|
||||
FindingID: "finding-2",
|
||||
Problem: "disk",
|
||||
Action: "cleanup",
|
||||
Outcome: ai.OutcomePartial,
|
||||
Automatic: false,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
if err := log.Log(record); err != nil {
|
||||
t.Fatalf("log remediation: %v", err)
|
||||
}
|
||||
|
||||
patrol := svc.GetPatrolService()
|
||||
setUnexportedField(t, patrol, "remediationLog", log)
|
||||
|
||||
handler := &AISettingsHandler{legacyAIService: svc}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/remediations?finding_id=finding-2", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.HandleGetRemediations(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["count"] != float64(1) {
|
||||
t.Fatalf("expected count 1, got %#v", payload["count"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetRemediations_LicenseHeader(t *testing.T) {
|
||||
handler := &AISettingsHandler{}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/remediations", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
handler.HandleGetRemediations(rec, req)
|
||||
|
||||
if rec.Header().Get("X-License-Required") != "true" {
|
||||
t.Fatalf("expected license header to be set")
|
||||
}
|
||||
if rec.Header().Get("X-License-Feature") != license.FeatureAIAutoFix {
|
||||
t.Fatalf("expected license feature header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetRemediationPlans_StatusMapping(t *testing.T) {
|
||||
engine := remediation.NewEngine(remediation.EngineConfig{})
|
||||
plan := &remediation.RemediationPlan{
|
||||
ID: "plan-1",
|
||||
Title: "Critical fix",
|
||||
Description: "fix it",
|
||||
RiskLevel: remediation.RiskCritical,
|
||||
Steps: []remediation.RemediationStep{
|
||||
{Order: 0, Command: "echo ok"},
|
||||
},
|
||||
}
|
||||
if err := engine.CreatePlan(plan); err != nil {
|
||||
t.Fatalf("CreatePlan: %v", err)
|
||||
}
|
||||
if _, err := engine.ApprovePlan(plan.ID, "tester"); err != nil {
|
||||
t.Fatalf("ApprovePlan: %v", err)
|
||||
}
|
||||
|
||||
handler := &AISettingsHandler{}
|
||||
handler.SetRemediationEngine(engine)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/remediation/plans?limit=5", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleGetRemediationPlans(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
plans := payload["plans"].([]interface{})
|
||||
if len(plans) != 1 {
|
||||
t.Fatalf("expected 1 plan, got %d", len(plans))
|
||||
}
|
||||
planView := plans[0].(map[string]interface{})
|
||||
if planView["risk_level"] != string(remediation.RiskHigh) {
|
||||
t.Fatalf("expected risk_level high, got %#v", planView["risk_level"])
|
||||
}
|
||||
if planView["status"] != "approved" {
|
||||
t.Fatalf("expected status approved, got %#v", planView["status"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleGetRemediationPlan_Success(t *testing.T) {
|
||||
engine := remediation.NewEngine(remediation.EngineConfig{})
|
||||
plan := &remediation.RemediationPlan{
|
||||
ID: "plan-2",
|
||||
Title: "Fix",
|
||||
Steps: []remediation.RemediationStep{{Order: 0, Command: "echo ok"}},
|
||||
}
|
||||
if err := engine.CreatePlan(plan); err != nil {
|
||||
t.Fatalf("CreatePlan: %v", err)
|
||||
}
|
||||
|
||||
handler := &AISettingsHandler{}
|
||||
handler.SetRemediationEngine(engine)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/ai/remediation/plans/plan-2?plan_id=plan-2", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleGetRemediationPlan(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
var got remediation.RemediationPlan
|
||||
if err := json.NewDecoder(rec.Body).Decode(&got); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if got.ID != "plan-2" {
|
||||
t.Fatalf("unexpected plan id: %s", got.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleApproveRemediationPlan_Success(t *testing.T) {
|
||||
engine := remediation.NewEngine(remediation.EngineConfig{})
|
||||
plan := &remediation.RemediationPlan{
|
||||
ID: "plan-3",
|
||||
Title: "Approve",
|
||||
Steps: []remediation.RemediationStep{{Order: 0, Command: "echo ok"}},
|
||||
}
|
||||
if err := engine.CreatePlan(plan); err != nil {
|
||||
t.Fatalf("CreatePlan: %v", err)
|
||||
}
|
||||
|
||||
handler := &AISettingsHandler{}
|
||||
handler.SetRemediationEngine(engine)
|
||||
|
||||
body := []byte(`{"plan_id":"plan-3"}`)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/ai/remediation/plans/plan-3/approve", bytes.NewReader(body))
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleApproveRemediationPlan(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
}
|
||||
98
internal/api/docker_agents_error_test.go
Normal file
98
internal/api/docker_agents_error_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
)
|
||||
|
||||
func TestDockerAgentHandlers_SetMonitorAndTenant(t *testing.T) {
|
||||
handler := &DockerAgentHandlers{}
|
||||
monitor := &monitoring.Monitor{}
|
||||
handler.SetMonitor(monitor)
|
||||
if handler.legacyMonitor != monitor {
|
||||
t.Fatalf("expected legacy monitor to be set")
|
||||
}
|
||||
|
||||
mtm := &monitoring.MultiTenantMonitor{}
|
||||
setUnexportedField(t, mtm, "monitors", map[string]*monitoring.Monitor{
|
||||
"default": monitor,
|
||||
})
|
||||
|
||||
handler.SetMultiTenantMonitor(mtm)
|
||||
if handler.mtMonitor != mtm {
|
||||
t.Fatalf("expected multi-tenant monitor to be set")
|
||||
}
|
||||
if handler.legacyMonitor != monitor {
|
||||
t.Fatalf("expected legacy monitor to be set from multi-tenant default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerAgentHandlers_GetMonitorFallback(t *testing.T) {
|
||||
legacy := &monitoring.Monitor{}
|
||||
handler := &DockerAgentHandlers{legacyMonitor: legacy}
|
||||
|
||||
if got := handler.getMonitor(context.Background()); got != legacy {
|
||||
t.Fatalf("expected legacy monitor fallback")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerAgentHandlers_HandleReport_Errors(t *testing.T) {
|
||||
handler := &DockerAgentHandlers{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/agents/docker/report", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleReport(rec, req)
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected 405, got %d", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/agents/docker/report", bytes.NewReader([]byte("{bad")))
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleReport(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerAgentHandlers_HandleCommandAck_Errors(t *testing.T) {
|
||||
handler := &DockerAgentHandlers{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/agents/docker/commands/123", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleCommandAck(rec, req)
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected 404, got %d", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/agents/docker/commands//ack", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleCommandAck(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/agents/docker/commands/cmd-1/ack", bytes.NewReader([]byte("{bad")))
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleCommandAck(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
|
||||
payload := map[string]string{
|
||||
"hostId": "host-1",
|
||||
"status": "unknown",
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/agents/docker/commands/cmd-2/ack", bytes.NewReader(body))
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleCommandAck(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
123
internal/api/docker_agents_routes_more_test.go
Normal file
123
internal/api/docker_agents_routes_more_test.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
)
|
||||
|
||||
func TestNewDockerAgentHandlers_DefaultMonitorFromMultiTenant(t *testing.T) {
|
||||
monitor := &monitoring.Monitor{}
|
||||
mtm := &monitoring.MultiTenantMonitor{}
|
||||
setUnexportedField(t, mtm, "monitors", map[string]*monitoring.Monitor{
|
||||
"default": monitor,
|
||||
})
|
||||
|
||||
handler := NewDockerAgentHandlers(mtm, nil, nil, nil)
|
||||
if handler.legacyMonitor != monitor {
|
||||
t.Fatalf("expected legacy monitor to be set from multi-tenant default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerAgentHandlers_HandleDockerHostActions_Routes(t *testing.T) {
|
||||
handler, monitor := newDockerAgentHandlers(t, nil)
|
||||
hostID := seedDockerHost(t, monitor)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/agents/docker/hosts/"+hostID+"/allow-reenroll", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleDockerHostActions(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("allow-reenroll status = %d, want 200", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodPut, "/api/agents/docker/hosts/"+hostID+"/unhide", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleDockerHostActions(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("unhide status = %d, want 200", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodPut, "/api/agents/docker/hosts/"+hostID+"/pending-uninstall", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleDockerHostActions(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("pending-uninstall status = %d, want 200", rec.Code)
|
||||
}
|
||||
|
||||
body := []byte(`{"displayName":"New Name"}`)
|
||||
req = httptest.NewRequest(http.MethodPut, "/api/agents/docker/hosts/"+hostID+"/display-name", bytes.NewReader(body))
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleDockerHostActions(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("display-name status = %d, want 200", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/agents/docker/hosts/"+hostID+"/check-updates", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleDockerHostActions(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("check-updates status = %d, want 200", rec.Code)
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), "Check for updates") {
|
||||
t.Fatalf("expected check updates message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerAgentHandlers_HandleDockerHostActions_DeleteRoute(t *testing.T) {
|
||||
handler, monitor := newDockerAgentHandlers(t, nil)
|
||||
hostID := seedDockerHost(t, monitor)
|
||||
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/agents/docker/hosts/"+hostID, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleDockerHostActions(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("delete status = %d, want 200", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerAgentHandlers_HandleDockerHostActions_MethodNotAllowed(t *testing.T) {
|
||||
handler := &DockerAgentHandlers{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/agents/docker/hosts/host-1/unknown", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleDockerHostActions(rec, req)
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected 405, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerAgentHandlers_HandleDeleteHost_Errors(t *testing.T) {
|
||||
handler, _ := newDockerAgentHandlers(t, nil)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/agents/docker/hosts/host-1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleDeleteHost(rec, req)
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected 405, got %d", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodDelete, "/api/agents/docker/hosts/", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleDeleteHost(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodDelete, "/api/agents/docker/hosts/missing?hide=true", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleDeleteHost(rec, req)
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected 404, got %d", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodDelete, "/api/agents/docker/hosts/missing?force=true", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleDeleteHost(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
104
internal/api/kubernetes_agents_error_test.go
Normal file
104
internal/api/kubernetes_agents_error_test.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
)
|
||||
|
||||
func TestKubernetesAgentHandlers_SetMultiTenantMonitor(t *testing.T) {
|
||||
handler := &KubernetesAgentHandlers{}
|
||||
monitor := &monitoring.Monitor{}
|
||||
|
||||
mtm := &monitoring.MultiTenantMonitor{}
|
||||
setUnexportedField(t, mtm, "monitors", map[string]*monitoring.Monitor{
|
||||
"default": monitor,
|
||||
})
|
||||
|
||||
handler.SetMultiTenantMonitor(mtm)
|
||||
if handler.mtMonitor != mtm {
|
||||
t.Fatalf("expected multi-tenant monitor to be set")
|
||||
}
|
||||
if handler.legacyMonitor != monitor {
|
||||
t.Fatalf("expected legacy monitor to be set from multi-tenant default")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubernetesAgentHandlers_HandleReport_Errors(t *testing.T) {
|
||||
handler := &KubernetesAgentHandlers{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/agents/kubernetes/report", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleReport(rec, req)
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected 405, got %d", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/agents/kubernetes/report", bytes.NewReader([]byte("{bad")))
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleReport(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubernetesAgentHandlers_HandleClusterActions_MethodNotAllowed(t *testing.T) {
|
||||
handler := &KubernetesAgentHandlers{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/agents/kubernetes/clusters/cluster-1/unknown", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleClusterActions(rec, req)
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected 405, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubernetesAgentHandlers_HandleDeleteCluster_Errors(t *testing.T) {
|
||||
handler, _ := newKubernetesAgentHandlers(t, nil)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/agents/kubernetes/clusters/cluster-1", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleDeleteCluster(rec, req)
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected 405, got %d", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodDelete, "/api/agents/kubernetes/clusters/", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleDeleteCluster(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodDelete, "/api/agents/kubernetes/clusters/missing", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
handler.HandleDeleteCluster(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubernetesAgentHandlers_HandleAllowReenroll_MissingID(t *testing.T) {
|
||||
handler := &KubernetesAgentHandlers{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/agents/kubernetes/clusters//allow-reenroll", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleAllowReenroll(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubernetesAgentHandlers_HandleSetCustomDisplayName_InvalidJSON(t *testing.T) {
|
||||
handler := &KubernetesAgentHandlers{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPut, "/api/agents/kubernetes/clusters/cluster-1/display-name", bytes.NewReader([]byte("{bad")))
|
||||
rec := httptest.NewRecorder()
|
||||
handler.HandleSetCustomDisplayName(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
18
internal/api/log_redact_test.go
Normal file
18
internal/api/log_redact_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package api
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSafePrefixForLog(t *testing.T) {
|
||||
if got := safePrefixForLog("value", 0); got != "" {
|
||||
t.Fatalf("expected empty for n<=0, got %q", got)
|
||||
}
|
||||
if got := safePrefixForLog("", 3); got != "" {
|
||||
t.Fatalf("expected empty for empty value, got %q", got)
|
||||
}
|
||||
if got := safePrefixForLog("abc", 3); got != "abc" {
|
||||
t.Fatalf("expected full value when len<=n, got %q", got)
|
||||
}
|
||||
if got := safePrefixForLog("abcdef", 3); got != "abc" {
|
||||
t.Fatalf("expected prefix, got %q", got)
|
||||
}
|
||||
}
|
||||
96
internal/api/monitor_wrappers_test.go
Normal file
96
internal/api/monitor_wrappers_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/ai/memory"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/alerts"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/notifications"
|
||||
)
|
||||
|
||||
func TestNewAlertMonitorWrapper_Nil(t *testing.T) {
|
||||
if NewAlertMonitorWrapper(nil) != nil {
|
||||
t.Fatalf("expected nil wrapper for nil monitor")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNotificationMonitorWrapper_Nil(t *testing.T) {
|
||||
if NewNotificationMonitorWrapper(nil) != nil {
|
||||
t.Fatalf("expected nil wrapper for nil monitor")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlertMonitorWrapper_Delegates(t *testing.T) {
|
||||
monitor := &monitoring.Monitor{}
|
||||
state := models.NewState()
|
||||
alertManager := &alerts.Manager{}
|
||||
incidentStore := &memory.IncidentStore{}
|
||||
notificationMgr := ¬ifications.NotificationManager{}
|
||||
configPersist := &config.ConfigPersistence{}
|
||||
|
||||
setUnexportedField(t, monitor, "state", state)
|
||||
setUnexportedField(t, monitor, "alertManager", alertManager)
|
||||
setUnexportedField(t, monitor, "incidentStore", incidentStore)
|
||||
setUnexportedField(t, monitor, "notificationMgr", notificationMgr)
|
||||
setUnexportedField(t, monitor, "configPersist", configPersist)
|
||||
|
||||
wrapper := NewAlertMonitorWrapper(monitor)
|
||||
if wrapper == nil {
|
||||
t.Fatalf("expected wrapper for non-nil monitor")
|
||||
}
|
||||
|
||||
if wrapper.GetAlertManager() != alertManager {
|
||||
t.Fatalf("unexpected alert manager")
|
||||
}
|
||||
if wrapper.GetIncidentStore() != incidentStore {
|
||||
t.Fatalf("unexpected incident store")
|
||||
}
|
||||
if wrapper.GetNotificationManager() != notificationMgr {
|
||||
t.Fatalf("unexpected notification manager")
|
||||
}
|
||||
if wrapper.GetConfigPersistence() != configPersist {
|
||||
t.Fatalf("unexpected config persistence")
|
||||
}
|
||||
|
||||
expected := state.GetSnapshot()
|
||||
if got := wrapper.GetState(); !reflect.DeepEqual(got, expected) {
|
||||
t.Fatalf("unexpected state snapshot")
|
||||
}
|
||||
|
||||
wrapper.SyncAlertState()
|
||||
if state.ActiveAlerts == nil {
|
||||
t.Fatalf("expected active alerts to be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationMonitorWrapper_Delegates(t *testing.T) {
|
||||
monitor := &monitoring.Monitor{}
|
||||
state := models.NewState()
|
||||
notificationMgr := ¬ifications.NotificationManager{}
|
||||
configPersist := &config.ConfigPersistence{}
|
||||
|
||||
setUnexportedField(t, monitor, "state", state)
|
||||
setUnexportedField(t, monitor, "notificationMgr", notificationMgr)
|
||||
setUnexportedField(t, monitor, "configPersist", configPersist)
|
||||
|
||||
wrapper := NewNotificationMonitorWrapper(monitor)
|
||||
if wrapper == nil {
|
||||
t.Fatalf("expected wrapper for non-nil monitor")
|
||||
}
|
||||
|
||||
if wrapper.GetNotificationManager() != notificationMgr {
|
||||
t.Fatalf("unexpected notification manager")
|
||||
}
|
||||
if wrapper.GetConfigPersistence() != configPersist {
|
||||
t.Fatalf("unexpected config persistence")
|
||||
}
|
||||
|
||||
expected := state.GetSnapshot()
|
||||
if got := wrapper.GetState(); !reflect.DeepEqual(got, expected) {
|
||||
t.Fatalf("unexpected state snapshot")
|
||||
}
|
||||
}
|
||||
134
internal/api/notification_queue_error_test.go
Normal file
134
internal/api/notification_queue_error_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/notifications"
|
||||
)
|
||||
|
||||
func TestNotificationQueueHandlers_GetDLQ_MissingScope(t *testing.T) {
|
||||
handler := &NotificationQueueHandlers{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/notifications/dlq", nil)
|
||||
record := &config.APITokenRecord{Scopes: []string{config.ScopeMonitoringWrite}}
|
||||
attachAPITokenRecord(req, record)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
handler.GetDLQ(rec, req)
|
||||
|
||||
if rec.Code != http.StatusForbidden {
|
||||
t.Fatalf("expected 403, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationQueueHandlers_GetDLQ_QueueNil(t *testing.T) {
|
||||
monitor := &monitoring.Monitor{}
|
||||
setUnexportedField(t, monitor, "notificationMgr", ¬ifications.NotificationManager{})
|
||||
handler := NewNotificationQueueHandlers(monitor)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/notifications/dlq", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.GetDLQ(rec, req)
|
||||
|
||||
if rec.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("expected 503, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationQueueHandlers_GetQueueStats_QueueNil(t *testing.T) {
|
||||
monitor := &monitoring.Monitor{}
|
||||
setUnexportedField(t, monitor, "notificationMgr", ¬ifications.NotificationManager{})
|
||||
handler := NewNotificationQueueHandlers(monitor)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/notifications/queue/stats", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.GetQueueStats(rec, req)
|
||||
|
||||
if rec.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("expected 503, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationQueueHandlers_RetryDLQItem_Errors(t *testing.T) {
|
||||
handler := &NotificationQueueHandlers{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/notifications/dlq/retry", bytes.NewReader([]byte("{bad")))
|
||||
rec := httptest.NewRecorder()
|
||||
handler.RetryDLQItem(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/notifications/dlq/retry", bytes.NewReader([]byte(`{"id":""}`)))
|
||||
rec = httptest.NewRecorder()
|
||||
handler.RetryDLQItem(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationQueueHandlers_RetryDLQItem_QueueNil(t *testing.T) {
|
||||
monitor := &monitoring.Monitor{}
|
||||
setUnexportedField(t, monitor, "notificationMgr", ¬ifications.NotificationManager{})
|
||||
handler := NewNotificationQueueHandlers(monitor)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/notifications/dlq/retry", bytes.NewReader([]byte(`{"id":"missing"}`)))
|
||||
rec := httptest.NewRecorder()
|
||||
handler.RetryDLQItem(rec, req)
|
||||
if rec.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("expected 503, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationQueueHandlers_DeleteDLQItem_Errors(t *testing.T) {
|
||||
handler := &NotificationQueueHandlers{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/notifications/dlq/delete", bytes.NewReader([]byte("{bad")))
|
||||
rec := httptest.NewRecorder()
|
||||
handler.DeleteDLQItem(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/notifications/dlq/delete", bytes.NewReader([]byte(`{"id":""}`)))
|
||||
rec = httptest.NewRecorder()
|
||||
handler.DeleteDLQItem(rec, req)
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationQueueHandlers_DeleteDLQItem_QueueNil(t *testing.T) {
|
||||
monitor := &monitoring.Monitor{}
|
||||
setUnexportedField(t, monitor, "notificationMgr", ¬ifications.NotificationManager{})
|
||||
handler := NewNotificationQueueHandlers(monitor)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/notifications/dlq/delete", bytes.NewReader([]byte(`{"id":"missing"}`)))
|
||||
rec := httptest.NewRecorder()
|
||||
handler.DeleteDLQItem(rec, req)
|
||||
if rec.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("expected 503, got %d", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationQueueHandlers_GetDLQ_InvalidLimit(t *testing.T) {
|
||||
handler, _ := newNotificationQueueHandlers(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/notifications/dlq?limit=invalid", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
handler.GetDLQ(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d", rec.Code)
|
||||
}
|
||||
|
||||
var dlq []notifications.QueuedNotification
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &dlq); err != nil {
|
||||
t.Fatalf("decode dlq: %v", err)
|
||||
}
|
||||
}
|
||||
389
internal/api/router_handlers_additional_test.go
Normal file
389
internal/api/router_handlers_additional_test.go
Normal file
@@ -0,0 +1,389 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
)
|
||||
|
||||
func TestHandleConfig_MethodNotAllowed(t *testing.T) {
|
||||
router := &Router{config: &config.Config{}}
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/config", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleConfig(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleConfig_Success(t *testing.T) {
|
||||
router := &Router{config: &config.Config{AutoUpdateEnabled: true, UpdateChannel: "beta"}}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/config", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleConfig(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["csrfProtection"] != false {
|
||||
t.Fatalf("expected csrfProtection=false, got %#v", payload["csrfProtection"])
|
||||
}
|
||||
if payload["autoUpdateEnabled"] != true {
|
||||
t.Fatalf("expected autoUpdateEnabled=true, got %#v", payload["autoUpdateEnabled"])
|
||||
}
|
||||
if payload["updateChannel"] != "beta" {
|
||||
t.Fatalf("expected updateChannel=beta, got %#v", payload["updateChannel"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleBackups_MethodNotAllowed(t *testing.T) {
|
||||
router := &Router{monitor: nil}
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/backups", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleBackups(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleBackups_Success(t *testing.T) {
|
||||
monitor, state, _ := newTestMonitor(t)
|
||||
state.PVEBackups = models.PVEBackups{
|
||||
BackupTasks: []models.BackupTask{{ID: "task-2"}},
|
||||
StorageBackups: []models.StorageBackup{{ID: "storage-1"}},
|
||||
GuestSnapshots: []models.GuestSnapshot{{ID: "snap-1"}},
|
||||
}
|
||||
state.PBSBackups = []models.PBSBackup{{ID: "pbs-1"}}
|
||||
state.PMGBackups = []models.PMGBackup{{ID: "pmg-1"}}
|
||||
|
||||
router := &Router{monitor: monitor}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/backups", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleBackups(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
var payload struct {
|
||||
Backups models.Backups `json:"backups"`
|
||||
PVEBackups models.PVEBackups `json:"pveBackups"`
|
||||
PBSBackups []models.PBSBackup `json:"pbsBackups"`
|
||||
PMGBackups []models.PMGBackup `json:"pmgBackups"`
|
||||
BackupTasks []models.BackupTask `json:"backupTasks"`
|
||||
Storage []models.StorageBackup `json:"storageBackups"`
|
||||
GuestSnaps []models.GuestSnapshot `json:"guestSnapshots"`
|
||||
}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if len(payload.Backups.PBS) != 1 || payload.Backups.PBS[0].ID != "pbs-1" {
|
||||
t.Fatalf("unexpected backups PBS data: %#v", payload.Backups.PBS)
|
||||
}
|
||||
if len(payload.PVEBackups.BackupTasks) != 1 || payload.PVEBackups.BackupTasks[0].ID != "task-2" {
|
||||
t.Fatalf("unexpected pveBackups data: %#v", payload.PVEBackups.BackupTasks)
|
||||
}
|
||||
if len(payload.BackupTasks) != 1 || payload.BackupTasks[0].ID != "task-2" {
|
||||
t.Fatalf("unexpected backupTasks data: %#v", payload.BackupTasks)
|
||||
}
|
||||
if len(payload.Storage) != 1 || payload.Storage[0].ID != "storage-1" {
|
||||
t.Fatalf("unexpected storageBackups data: %#v", payload.Storage)
|
||||
}
|
||||
if len(payload.GuestSnaps) != 1 || payload.GuestSnaps[0].ID != "snap-1" {
|
||||
t.Fatalf("unexpected guestSnapshots data: %#v", payload.GuestSnaps)
|
||||
}
|
||||
if len(payload.PBSBackups) != 1 || payload.PBSBackups[0].ID != "pbs-1" {
|
||||
t.Fatalf("unexpected pbsBackups data: %#v", payload.PBSBackups)
|
||||
}
|
||||
if len(payload.PMGBackups) != 1 || payload.PMGBackups[0].ID != "pmg-1" {
|
||||
t.Fatalf("unexpected pmgBackups data: %#v", payload.PMGBackups)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleBackupsPVE_Empty(t *testing.T) {
|
||||
monitor, state, _ := newTestMonitor(t)
|
||||
state.PVEBackups = models.PVEBackups{}
|
||||
|
||||
router := &Router{monitor: monitor}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/backups/pve", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleBackupsPVE(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
var payload struct {
|
||||
Backups []models.StorageBackup `json:"backups"`
|
||||
}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload.Backups == nil || len(payload.Backups) != 0 {
|
||||
t.Fatalf("expected empty backups array, got %#v", payload.Backups)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleBackupsPBS_Empty(t *testing.T) {
|
||||
monitor, state, _ := newTestMonitor(t)
|
||||
state.PBSInstances = nil
|
||||
|
||||
router := &Router{monitor: monitor}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/backups/pbs", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleBackupsPBS(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
var payload struct {
|
||||
Instances []models.PBSInstance `json:"instances"`
|
||||
}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload.Instances == nil || len(payload.Instances) != 0 {
|
||||
t.Fatalf("expected empty instances array, got %#v", payload.Instances)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSnapshots_Empty(t *testing.T) {
|
||||
monitor, state, _ := newTestMonitor(t)
|
||||
state.PVEBackups = models.PVEBackups{}
|
||||
|
||||
router := &Router{monitor: monitor}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/snapshots", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleSnapshots(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
var payload struct {
|
||||
Snapshots []models.GuestSnapshot `json:"snapshots"`
|
||||
}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload.Snapshots == nil || len(payload.Snapshots) != 0 {
|
||||
t.Fatalf("expected empty snapshots array, got %#v", payload.Snapshots)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSimpleStats(t *testing.T) {
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodGet, "/simple-stats", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleSimpleStats(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
if ct := rec.Header().Get("Content-Type"); ct != "text/html; charset=utf-8" {
|
||||
t.Fatalf("expected text/html content type, got %q", ct)
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), "Simple Pulse Stats") {
|
||||
t.Fatalf("expected stats page HTML, got %q", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSocketIO_RedirectsForJS(t *testing.T) {
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodGet, "/socket.io/socket.io.js", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleSocketIO(rec, req)
|
||||
|
||||
if rec.Code != http.StatusFound {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusFound, rec.Code)
|
||||
}
|
||||
if location := rec.Header().Get("Location"); location != "https://cdn.socket.io/4.8.1/socket.io.min.js" {
|
||||
t.Fatalf("unexpected redirect location: %q", location)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSocketIO_PollingHandshake(t *testing.T) {
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodGet, "/socket.io/?transport=polling", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleSocketIO(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
if ct := rec.Header().Get("Content-Type"); ct != "text/plain; charset=UTF-8" {
|
||||
t.Fatalf("expected text/plain content type, got %q", ct)
|
||||
}
|
||||
body := rec.Body.String()
|
||||
if !strings.HasPrefix(body, "0{") || !strings.Contains(body, "\"sid\"") {
|
||||
t.Fatalf("unexpected polling handshake body: %q", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSocketIO_PollingConnected(t *testing.T) {
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodGet, "/socket.io/?transport=polling&sid=abc", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleSocketIO(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
if body := rec.Body.String(); body != "6" {
|
||||
t.Fatalf("unexpected polling body: %q", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleSocketIO_DefaultRedirect(t *testing.T) {
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodGet, "/socket.io/?foo=bar", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleSocketIO(rec, req)
|
||||
|
||||
if rec.Code != http.StatusFound {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusFound, rec.Code)
|
||||
}
|
||||
if location := rec.Header().Get("Location"); location != "/ws" {
|
||||
t.Fatalf("unexpected redirect location: %q", location)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDownloadInstallScript_Fallback(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
scriptPath := filepath.Join(root, "scripts", "install-docker-agent.sh")
|
||||
if err := os.MkdirAll(filepath.Dir(scriptPath), 0o755); err != nil {
|
||||
t.Fatalf("mkdir scripts dir: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(scriptPath, []byte("#!/bin/sh\necho hi\n"), 0o644); err != nil {
|
||||
t.Fatalf("write script: %v", err)
|
||||
}
|
||||
|
||||
router := &Router{projectRoot: root}
|
||||
req := httptest.NewRequest(http.MethodGet, "/download/install-docker-agent.sh", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleDownloadInstallScript(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
if cache := rec.Header().Get("Cache-Control"); !strings.Contains(cache, "no-cache") {
|
||||
t.Fatalf("expected no-cache header, got %q", cache)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDownloadHostAgentInstallScript_Fallback(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
scriptPath := filepath.Join(root, "scripts", "install.sh")
|
||||
if err := os.MkdirAll(filepath.Dir(scriptPath), 0o755); err != nil {
|
||||
t.Fatalf("mkdir scripts dir: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(scriptPath, []byte("#!/bin/sh\necho host\n"), 0o644); err != nil {
|
||||
t.Fatalf("write script: %v", err)
|
||||
}
|
||||
|
||||
router := &Router{projectRoot: root}
|
||||
req := httptest.NewRequest(http.MethodGet, "/download/install.sh", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleDownloadHostAgentInstallScript(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
if cache := rec.Header().Get("Cache-Control"); !strings.Contains(cache, "no-cache") {
|
||||
t.Fatalf("expected no-cache header, got %q", cache)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDownloadAgent_Found(t *testing.T) {
|
||||
binDir := t.TempDir()
|
||||
t.Setenv("PULSE_BIN_DIR", binDir)
|
||||
payload := []byte("docker-agent-binary")
|
||||
filePath := filepath.Join(binDir, "pulse-docker-agent-linux-arm64")
|
||||
if err := os.WriteFile(filePath, payload, 0o755); err != nil {
|
||||
t.Fatalf("write binary: %v", err)
|
||||
}
|
||||
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodGet, "/download/pulse-docker-agent?arch=arm64", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleDownloadAgent(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code)
|
||||
}
|
||||
expected := fmt.Sprintf("%x", sha256.Sum256(payload))
|
||||
if checksum := rec.Header().Get("X-Checksum-Sha256"); checksum != expected {
|
||||
t.Fatalf("unexpected checksum header: %q", checksum)
|
||||
}
|
||||
if rec.Body.String() != string(payload) {
|
||||
t.Fatalf("unexpected response body: %q", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDownloadAgent_NotFound(t *testing.T) {
|
||||
binDir := t.TempDir()
|
||||
t.Setenv("PULSE_BIN_DIR", binDir)
|
||||
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodGet, "/download/pulse-docker-agent", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleDownloadAgent(rec, req)
|
||||
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusNotFound, rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadScript_MethodNotAllowed(t *testing.T) {
|
||||
router := &Router{}
|
||||
cases := []struct {
|
||||
name string
|
||||
handler func(http.ResponseWriter, *http.Request)
|
||||
}{
|
||||
{name: "container-install", handler: router.handleDownloadContainerAgentInstallScript},
|
||||
{name: "host-install-ps", handler: router.handleDownloadHostAgentInstallScriptPS},
|
||||
{name: "host-uninstall", handler: router.handleDownloadHostAgentUninstallScript},
|
||||
{name: "host-uninstall-ps", handler: router.handleDownloadHostAgentUninstallScriptPS},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "/download", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
tc.handler(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
143
internal/api/router_helpers_more_test.go
Normal file
143
internal/api/router_helpers_more_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
)
|
||||
|
||||
func TestRouterGetMonitor_Defaults(t *testing.T) {
|
||||
defaultMonitor, _, _ := newTestMonitor(t)
|
||||
router := &Router{monitor: defaultMonitor}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
monitor, err := router.getMonitor(req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if monitor != defaultMonitor {
|
||||
t.Fatalf("expected default monitor to be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterGetMonitor_WithTenant(t *testing.T) {
|
||||
defaultMonitor, _, _ := newTestMonitor(t)
|
||||
tenantMonitor, _, _ := newTestMonitor(t)
|
||||
|
||||
mtm := &monitoring.MultiTenantMonitor{}
|
||||
setUnexportedField(t, mtm, "monitors", map[string]*monitoring.Monitor{
|
||||
"tenant-1": tenantMonitor,
|
||||
})
|
||||
|
||||
router := &Router{monitor: defaultMonitor, mtMonitor: mtm}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
ctx := context.WithValue(req.Context(), OrgIDContextKey, "tenant-1")
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
monitor, err := router.getMonitor(req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if monitor != tenantMonitor {
|
||||
t.Fatalf("expected tenant monitor to be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiTenantStateProvider_DefaultAndTenant(t *testing.T) {
|
||||
defaultMonitor, defaultState, _ := newTestMonitor(t)
|
||||
defaultState.VMs = []models.VM{{ID: "vm-default"}}
|
||||
|
||||
tenantMonitor, tenantState, _ := newTestMonitor(t)
|
||||
tenantState.VMs = []models.VM{{ID: "vm-tenant"}}
|
||||
|
||||
mtm := &monitoring.MultiTenantMonitor{}
|
||||
setUnexportedField(t, mtm, "monitors", map[string]*monitoring.Monitor{
|
||||
"tenant-1": tenantMonitor,
|
||||
})
|
||||
|
||||
provider := NewMultiTenantStateProvider(mtm, defaultMonitor)
|
||||
|
||||
snapDefault := provider.GetStateForTenant("")
|
||||
if len(snapDefault.VMs) != 1 || snapDefault.VMs[0].ID != "vm-default" {
|
||||
t.Fatalf("unexpected default snapshot: %#v", snapDefault.VMs)
|
||||
}
|
||||
snapDefault = provider.GetStateForTenant("default")
|
||||
if len(snapDefault.VMs) != 1 || snapDefault.VMs[0].ID != "vm-default" {
|
||||
t.Fatalf("unexpected default snapshot: %#v", snapDefault.VMs)
|
||||
}
|
||||
|
||||
snapTenant := provider.GetStateForTenant("tenant-1")
|
||||
if len(snapTenant.VMs) != 1 || snapTenant.VMs[0].ID != "vm-tenant" {
|
||||
t.Fatalf("unexpected tenant snapshot: %#v", snapTenant.VMs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiTenantStateProvider_FallbackOnError(t *testing.T) {
|
||||
defaultMonitor, defaultState, _ := newTestMonitor(t)
|
||||
defaultState.VMs = []models.VM{{ID: "vm-default"}}
|
||||
|
||||
mtp := config.NewMultiTenantPersistence(t.TempDir())
|
||||
mtm := monitoring.NewMultiTenantMonitor(&config.Config{}, mtp, nil)
|
||||
defer mtm.Stop()
|
||||
|
||||
provider := NewMultiTenantStateProvider(mtm, defaultMonitor)
|
||||
|
||||
snap := provider.GetStateForTenant("../bad")
|
||||
if len(snap.VMs) != 1 || snap.VMs[0].ID != "vm-default" {
|
||||
t.Fatalf("expected fallback snapshot, got %#v", snap.VMs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetMultiTenantMonitor_WiresHandlers(t *testing.T) {
|
||||
defaultMonitor, _, _ := newTestMonitor(t)
|
||||
mtm := &monitoring.MultiTenantMonitor{}
|
||||
setUnexportedField(t, mtm, "monitors", map[string]*monitoring.Monitor{
|
||||
"default": defaultMonitor,
|
||||
})
|
||||
|
||||
router := &Router{
|
||||
alertHandlers: &AlertHandlers{},
|
||||
notificationHandlers: &NotificationHandlers{},
|
||||
dockerAgentHandlers: &DockerAgentHandlers{},
|
||||
hostAgentHandlers: &HostAgentHandlers{},
|
||||
kubernetesAgentHandlers: &KubernetesAgentHandlers{},
|
||||
systemSettingsHandler: &SystemSettingsHandler{},
|
||||
resourceHandlers: &ResourceHandlers{},
|
||||
}
|
||||
|
||||
router.SetMultiTenantMonitor(mtm)
|
||||
|
||||
if router.mtMonitor != mtm {
|
||||
t.Fatalf("expected router mtMonitor to be updated")
|
||||
}
|
||||
if router.monitor != defaultMonitor {
|
||||
t.Fatalf("expected router monitor to be set to default monitor")
|
||||
}
|
||||
if router.alertHandlers.mtMonitor != mtm {
|
||||
t.Fatalf("expected alertHandlers mtMonitor to be set")
|
||||
}
|
||||
if router.notificationHandlers.mtMonitor != mtm {
|
||||
t.Fatalf("expected notificationHandlers mtMonitor to be set")
|
||||
}
|
||||
if router.dockerAgentHandlers.mtMonitor != mtm {
|
||||
t.Fatalf("expected dockerAgentHandlers mtMonitor to be set")
|
||||
}
|
||||
if router.hostAgentHandlers.mtMonitor != mtm {
|
||||
t.Fatalf("expected hostAgentHandlers mtMonitor to be set")
|
||||
}
|
||||
if router.kubernetesAgentHandlers.mtMonitor != mtm {
|
||||
t.Fatalf("expected kubernetesAgentHandlers mtMonitor to be set")
|
||||
}
|
||||
if router.systemSettingsHandler.mtMonitor != mtm {
|
||||
t.Fatalf("expected systemSettingsHandler mtMonitor to be set")
|
||||
}
|
||||
if router.resourceHandlers.tenantStateProvider == nil {
|
||||
t.Fatalf("expected tenant state provider to be set")
|
||||
}
|
||||
}
|
||||
247
internal/api/router_low_coverage_additional_test.go
Normal file
247
internal/api/router_low_coverage_additional_test.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/ai/knowledge"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
"github.com/rcourtman/pulse-go-rewrite/pkg/metrics"
|
||||
)
|
||||
|
||||
func TestHandleMetricsStoreStats_MethodNotAllowed(t *testing.T) {
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/metrics/store/stats", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleMetricsStoreStats(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMetricsStoreStats_NoMonitor(t *testing.T) {
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/metrics/store/stats", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleMetricsStoreStats(rec, req)
|
||||
|
||||
if rec.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMetricsStoreStats_NoStore(t *testing.T) {
|
||||
monitor, _, _ := newTestMonitor(t)
|
||||
router := &Router{monitor: monitor}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/metrics/store/stats", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleMetricsStoreStats(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["enabled"] != false {
|
||||
t.Fatalf("expected enabled=false, got %#v", payload["enabled"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMetricsStoreStats_WithStore(t *testing.T) {
|
||||
monitor, _, _ := newTestMonitor(t)
|
||||
store, err := metrics.NewStore(metrics.DefaultConfig(t.TempDir()))
|
||||
if err != nil {
|
||||
t.Fatalf("metrics.NewStore error: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
setUnexportedField(t, monitor, "metricsStore", store)
|
||||
router := &Router{monitor: monitor}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/metrics/store/stats", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleMetricsStoreStats(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["enabled"] != true {
|
||||
t.Fatalf("expected enabled=true, got %#v", payload["enabled"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDiagnosticsDockerPrepareToken_MethodNotAllowed(t *testing.T) {
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/diagnostics/docker/prepare-token", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleDiagnosticsDockerPrepareToken(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDiagnosticsDockerPrepareToken_InvalidJSON(t *testing.T) {
|
||||
router := &Router{monitor: &monitoring.Monitor{}, config: &config.Config{}}
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/diagnostics/docker/prepare-token", strings.NewReader("{"))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleDiagnosticsDockerPrepareToken(rec, req)
|
||||
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDiagnosticsDockerPrepareToken_MissingHostID(t *testing.T) {
|
||||
router := &Router{monitor: &monitoring.Monitor{}, config: &config.Config{}}
|
||||
body := bytes.NewBufferString(`{"hostId":""}`)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/diagnostics/docker/prepare-token", body)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleDiagnosticsDockerPrepareToken(rec, req)
|
||||
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDiagnosticsDockerPrepareToken_HostNotFound(t *testing.T) {
|
||||
monitor, _, _ := newTestMonitor(t)
|
||||
router := &Router{monitor: monitor, config: &config.Config{}}
|
||||
body := bytes.NewBufferString(`{"hostId":"missing"}`)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/diagnostics/docker/prepare-token", body)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleDiagnosticsDockerPrepareToken(rec, req)
|
||||
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDiagnosticsDockerPrepareToken_Success(t *testing.T) {
|
||||
monitor, state, _ := newTestMonitor(t)
|
||||
state.DockerHosts = []models.DockerHost{{ID: "host-1", DisplayName: "Docker Host"}}
|
||||
|
||||
router := &Router{monitor: monitor, config: &config.Config{PublicURL: "https://pulse.example.com"}}
|
||||
body := bytes.NewBufferString(`{"hostId":"host-1","tokenName":""}`)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/diagnostics/docker/prepare-token", body)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleDiagnosticsDockerPrepareToken(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if ok, _ := payload["success"].(bool); !ok {
|
||||
t.Fatalf("expected success true, got %#v", payload["success"])
|
||||
}
|
||||
if payload["token"] == "" {
|
||||
t.Fatalf("expected token in response")
|
||||
}
|
||||
host, _ := payload["host"].(map[string]interface{})
|
||||
if host["id"] != "host-1" {
|
||||
t.Fatalf("unexpected host id: %#v", host["id"])
|
||||
}
|
||||
if !strings.Contains(payload["installCommand"].(string), "https://pulse.example.com") {
|
||||
t.Fatalf("expected install command to include base URL")
|
||||
}
|
||||
if len(router.config.APITokens) == 0 {
|
||||
t.Fatalf("expected API token to be recorded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDownloadDockerInstallerScript_MethodNotAllowed(t *testing.T) {
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodPost, "/download/install-docker.sh", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleDownloadDockerInstallerScript(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDownloadDockerInstallerScript_ServesFile(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
scriptPath := filepath.Join(root, "scripts", "install-docker.sh")
|
||||
if err := os.MkdirAll(filepath.Dir(scriptPath), 0o755); err != nil {
|
||||
t.Fatalf("mkdir scripts dir: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(scriptPath, []byte("#!/bin/sh\necho docker\n"), 0o644); err != nil {
|
||||
t.Fatalf("write script: %v", err)
|
||||
}
|
||||
|
||||
router := &Router{projectRoot: root}
|
||||
req := httptest.NewRequest(http.MethodGet, "/download/install-docker.sh", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleDownloadDockerInstallerScript(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
if ct := rec.Header().Get("Content-Type"); ct != "text/x-shellscript" {
|
||||
t.Fatalf("expected text/x-shellscript, got %q", ct)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKnowledgeStoreProviderWrapper(t *testing.T) {
|
||||
wrapper := &knowledgeStoreProviderWrapper{}
|
||||
if err := wrapper.SaveNote("res-1", "note", "service"); err == nil {
|
||||
t.Fatalf("expected error when store is nil")
|
||||
}
|
||||
if got := wrapper.GetKnowledge("res-1", ""); got != nil {
|
||||
t.Fatalf("expected nil knowledge when store is nil")
|
||||
}
|
||||
|
||||
store, err := knowledge.NewStore(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatalf("knowledge.NewStore error: %v", err)
|
||||
}
|
||||
wrapper.store = store
|
||||
|
||||
if err := wrapper.SaveNote("res-1", "hello", "service"); err != nil {
|
||||
t.Fatalf("SaveNote error: %v", err)
|
||||
}
|
||||
|
||||
entries := wrapper.GetKnowledge("res-1", "service")
|
||||
if len(entries) != 1 {
|
||||
t.Fatalf("expected 1 entry, got %d", len(entries))
|
||||
}
|
||||
if entries[0].Category != "service" || entries[0].Note != "hello" {
|
||||
t.Fatalf("unexpected entry: %#v", entries[0])
|
||||
}
|
||||
|
||||
all := wrapper.GetKnowledge("res-1", "")
|
||||
if len(all) != 1 {
|
||||
t.Fatalf("expected 1 entry from full query, got %d", len(all))
|
||||
}
|
||||
}
|
||||
234
internal/api/router_version_tenant_metrics_test.go
Normal file
234
internal/api/router_version_tenant_metrics_test.go
Normal file
@@ -0,0 +1,234 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/updates"
|
||||
"github.com/rcourtman/pulse-go-rewrite/pkg/metrics"
|
||||
)
|
||||
|
||||
func TestGetTenantMonitor_Default(t *testing.T) {
|
||||
defaultMonitor, _, _ := newTestMonitor(t)
|
||||
router := &Router{monitor: defaultMonitor}
|
||||
|
||||
if got := router.getTenantMonitor(context.Background()); got != defaultMonitor {
|
||||
t.Fatalf("expected default monitor to be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTenantMonitor_WithTenant(t *testing.T) {
|
||||
defaultMonitor, _, _ := newTestMonitor(t)
|
||||
tenantMonitor, _, _ := newTestMonitor(t)
|
||||
|
||||
mtm := &monitoring.MultiTenantMonitor{}
|
||||
setUnexportedField(t, mtm, "monitors", map[string]*monitoring.Monitor{
|
||||
"tenant-1": tenantMonitor,
|
||||
})
|
||||
|
||||
router := &Router{monitor: defaultMonitor, mtMonitor: mtm}
|
||||
ctx := context.WithValue(context.Background(), OrgIDContextKey, "tenant-1")
|
||||
|
||||
if got := router.getTenantMonitor(ctx); got != tenantMonitor {
|
||||
t.Fatalf("expected tenant monitor to be returned")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTenantMonitor_FallbackOnError(t *testing.T) {
|
||||
defaultMonitor, _, _ := newTestMonitor(t)
|
||||
mtp := config.NewMultiTenantPersistence(t.TempDir())
|
||||
mtm := monitoring.NewMultiTenantMonitor(&config.Config{}, mtp, nil)
|
||||
defer mtm.Stop()
|
||||
|
||||
router := &Router{monitor: defaultMonitor, mtMonitor: mtm}
|
||||
ctx := context.WithValue(context.Background(), OrgIDContextKey, "../bad")
|
||||
|
||||
if got := router.getTenantMonitor(ctx); got != defaultMonitor {
|
||||
t.Fatalf("expected fallback to default monitor")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleVersion_MethodNotAllowed(t *testing.T) {
|
||||
router := &Router{updateManager: updates.NewManager(&config.Config{})}
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/version", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleVersion(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleVersion_Success(t *testing.T) {
|
||||
router := &Router{updateManager: updates.NewManager(&config.Config{})}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/version", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleVersion(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["version"] == "" {
|
||||
t.Fatalf("expected version in response, got %#v", payload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMetricsHistory_MethodNotAllowed(t *testing.T) {
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/metrics/history", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleMetricsHistory(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMetricsHistory_MissingParams(t *testing.T) {
|
||||
monitor, _, _ := newTestMonitor(t)
|
||||
router := &Router{monitor: monitor}
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/metrics/history", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleMetricsHistory(rec, req)
|
||||
|
||||
if rec.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMetricsHistory_LicenseRequired(t *testing.T) {
|
||||
monitor, _, _ := newTestMonitor(t)
|
||||
mtp := config.NewMultiTenantPersistence(t.TempDir())
|
||||
if _, err := mtp.GetPersistence("default"); err != nil {
|
||||
t.Fatalf("failed to init persistence: %v", err)
|
||||
}
|
||||
|
||||
router := &Router{
|
||||
monitor: monitor,
|
||||
licenseHandlers: NewLicenseHandlers(mtp),
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/metrics-store/history?resourceType=vm&resourceId=vm-1&range=30d", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleMetricsHistory(rec, req)
|
||||
|
||||
if rec.Code != http.StatusPaymentRequired {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusPaymentRequired)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMetricsHistory_UsesStore(t *testing.T) {
|
||||
monitor, _, _ := newTestMonitor(t)
|
||||
store, err := metrics.NewStore(metrics.DefaultConfig(t.TempDir()))
|
||||
if err != nil {
|
||||
t.Fatalf("metrics.NewStore error: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
store.WriteBatchSync([]metrics.WriteMetric{{
|
||||
ResourceType: "vm",
|
||||
ResourceID: "vm-1",
|
||||
MetricType: "cpu",
|
||||
Value: 42.0,
|
||||
Timestamp: time.Now(),
|
||||
Tier: metrics.TierRaw,
|
||||
}})
|
||||
|
||||
setUnexportedField(t, monitor, "metricsStore", store)
|
||||
router := &Router{monitor: monitor}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/metrics-store/history?resourceType=vm&resourceId=vm-1&metric=cpu&range=1h", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleMetricsHistory(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["source"] != "store" {
|
||||
t.Fatalf("expected source store, got %#v", payload["source"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleMetricsHistory_UsesStoreAllMetrics(t *testing.T) {
|
||||
monitor, _, _ := newTestMonitor(t)
|
||||
store, err := metrics.NewStore(metrics.DefaultConfig(t.TempDir()))
|
||||
if err != nil {
|
||||
t.Fatalf("metrics.NewStore error: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
now := time.Now()
|
||||
store.WriteBatchSync([]metrics.WriteMetric{
|
||||
{
|
||||
ResourceType: "vm",
|
||||
ResourceID: "vm-1",
|
||||
MetricType: "cpu",
|
||||
Value: 50.0,
|
||||
Timestamp: now,
|
||||
Tier: metrics.TierRaw,
|
||||
},
|
||||
{
|
||||
ResourceType: "vm",
|
||||
ResourceID: "vm-1",
|
||||
MetricType: "memory",
|
||||
Value: 70.0,
|
||||
Timestamp: now,
|
||||
Tier: metrics.TierRaw,
|
||||
},
|
||||
})
|
||||
|
||||
setUnexportedField(t, monitor, "metricsStore", store)
|
||||
router := &Router{monitor: monitor}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/metrics-store/history?resourceType=vm&resourceId=vm-1&range=1h", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleMetricsHistory(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode response: %v", err)
|
||||
}
|
||||
if payload["source"] != "store" {
|
||||
t.Fatalf("expected source store, got %#v", payload["source"])
|
||||
}
|
||||
metricsMap, ok := payload["metrics"].(map[string]interface{})
|
||||
if !ok || metricsMap["cpu"] == nil {
|
||||
t.Fatalf("expected cpu metrics in response, got %#v", payload["metrics"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleCharts_MethodNotAllowed(t *testing.T) {
|
||||
router := &Router{}
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/charts", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handleCharts(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user