Files
ITWorx-Pulse-Public/internal/alertapi/handler_test.go
T
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

122 lines
4.7 KiB
Go

package alertapi
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/itworx/pulse/internal/alert"
"github.com/itworx/pulse/internal/audit"
"github.com/itworx/pulse/internal/auth"
"github.com/itworx/pulse/internal/metriccatalog"
)
type fakeStore struct {
rule alert.Rule
getCalls, createCalls, updateCalls, toggleCalls int
}
func (f *fakeStore) Create(_ context.Context, _ string, document alert.Document, _ string) (alert.Rule, alert.Version, error) {
f.createCalls++
f.rule = alert.Rule{Document: document, Revision: 1, CurrentVersion: 1}
return f.rule, alert.Version{RuleID: document.ID, VersionNumber: 1, Document: document}, nil
}
func (f *fakeStore) Get(_ context.Context, _ string) (alert.Rule, error) {
f.getCalls++
return f.rule, nil
}
func (f *fakeStore) List(_ context.Context, _ int) ([]alert.Rule, error) {
return []alert.Rule{f.rule}, nil
}
func (f *fakeStore) Update(_ context.Context, id, _ string, _ int64, document alert.Document, _ string) (alert.Rule, error) {
f.updateCalls++
document.ID = id
f.rule.Document = document
f.rule.Revision++
return f.rule, nil
}
func (f *fakeStore) Versions(_ context.Context, _ string, _ int) ([]alert.Version, error) {
return []alert.Version{{Document: f.rule.Document, VersionNumber: 1}}, nil
}
func (f *fakeStore) SetEnabled(_ context.Context, _ string, _ int64, enabled bool) (alert.Rule, error) {
f.toggleCalls++
f.rule.Enabled = enabled
f.rule.Document.Enabled = enabled
f.rule.Revision++
return f.rule, nil
}
func requestWithPrincipal(method, path string, body any, role auth.Role) *http.Request {
data, _ := json.Marshal(body)
request := httptest.NewRequest(method, path, strings.NewReader(string(data)))
request.Header.Set("Content-Type", "application/json")
return request.WithContext(auth.WithPrincipal(request.Context(), auth.Principal{Subject: "editor-1", Role: role}))
}
func TestViewerCannotCreateAlertRule(t *testing.T) {
document, registry := validDocumentForHandler(t)
store := &fakeStore{}
handler := Handler{Repository: store, Registry: registry}
response := httptest.NewRecorder()
handler.ServeHTTP(response, requestWithPrincipal(http.MethodPost, "/api/v1/alert-rules", document, auth.RoleViewer))
if response.Code != http.StatusForbidden || store.createCalls != 0 {
t.Fatalf("status=%d creates=%d", response.Code, store.createCalls)
}
}
func TestPreviewDoesNotWriteOrAudit(t *testing.T) {
document, registry := validDocumentForHandler(t)
store := &fakeStore{}
auditStore := &audit.MemoryStore{}
handler := Handler{Repository: store, Registry: registry, Audit: auditStore}
response := httptest.NewRecorder()
handler.ServeHTTP(response, requestWithPrincipal(http.MethodPost, "/api/v1/alert-rules/"+document.ID+"/test", map[string]any{"rule": document, "value": 90}, auth.RoleEditor))
if response.Code != http.StatusOK {
t.Fatalf("preview status=%d body=%s", response.Code, response.Body.String())
}
if store.createCalls != 0 || store.updateCalls != 0 || store.toggleCalls != 0 || len(auditStore.Events) != 0 {
t.Fatalf("preview had side effects: store=%#v audit=%d", store, len(auditStore.Events))
}
var body map[string]any
if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
t.Fatal(err)
}
if body["preview"] == nil {
t.Fatal("preview result missing")
}
}
func TestEnableIsAudited(t *testing.T) {
document, registry := validDocumentForHandler(t)
store := &fakeStore{rule: alert.Rule{Document: document, Revision: 1}}
auditStore := &audit.MemoryStore{}
handler := Handler{Repository: store, Registry: registry, Audit: auditStore}
request := requestWithPrincipal(http.MethodPost, "/api/v1/alert-rules/"+document.ID+"/enable?revision=1", map[string]any{}, auth.RoleEditor)
response := httptest.NewRecorder()
handler.ServeHTTP(response, request)
if response.Code != http.StatusOK || store.toggleCalls != 1 {
t.Fatalf("status=%d toggles=%d", response.Code, store.toggleCalls)
}
if len(auditStore.Events) != 1 || auditStore.Events[0].Action != "alert_rule.enable" {
t.Fatalf("audit=%#v", auditStore.Events)
}
}
func validDocumentForHandler(t *testing.T) (alert.Document, metriccatalog.Registry) {
t.Helper()
registry, err := metriccatalog.DefaultRegistry()
if err != nil {
t.Fatal(err)
}
return alert.Document{
SchemaVersion: 1, ID: alert.NewID(), Name: "CPU aandacht", Severity: alert.SeverityAttention,
Scope: map[string]any{"entityType": "host"},
Condition: alert.Condition{InputType: "metric", Metric: registry.Metrics()[0].SemanticName, Operator: ">", Threshold: float64(80)},
EvaluationIntervalSeconds: 30, UnknownBehavior: alert.UnknownRetain,
Message: alert.Message{TitleKey: "alerts.cpu.title", BodyKey: "alerts.cpu.body"},
}, registry
}