Public source validation / validate (push) Failing after 3m8s
122 lines
4.7 KiB
Go
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
|
|
}
|