Public source validation / validate (push) Failing after 3m8s
106 lines
5.0 KiB
Go
106 lines
5.0 KiB
Go
package incidentapi
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/itworx/pulse/internal/audit"
|
|
"github.com/itworx/pulse/internal/auth"
|
|
"github.com/itworx/pulse/internal/incident"
|
|
)
|
|
|
|
type memoryStore struct {
|
|
item incident.Incident
|
|
association incident.Association
|
|
associations int
|
|
}
|
|
|
|
func (s *memoryStore) List(context.Context, int, incident.Status) ([]incident.Incident, error) {
|
|
return []incident.Incident{s.item}, nil
|
|
}
|
|
func (s *memoryStore) Get(context.Context, string) (incident.Incident, error) { return s.item, nil }
|
|
func (s *memoryStore) UpsertCandidate(context.Context, incident.Candidate) (incident.Incident, bool, error) {
|
|
return s.item, false, nil
|
|
}
|
|
func (s *memoryStore) AssociateAlert(_ context.Context, _, id, rationale string, confidence float64, actor string) (incident.Association, bool, error) {
|
|
s.associations++
|
|
s.association = incident.Association{AlertID: id, Rationale: rationale, Confidence: confidence, CorrelationMethod: "manual", Manual: true, AddedBy: actor}
|
|
return s.association, s.associations > 1, nil
|
|
}
|
|
func (s *memoryStore) DisassociateAlert(context.Context, string, string) error { return nil }
|
|
func (s *memoryStore) UpdateStatus(context.Context, string, incident.Status, int64, time.Time) (incident.Incident, error) {
|
|
return s.item, nil
|
|
}
|
|
|
|
func requestWithPrincipal(method, path, body string, role auth.Role) *http.Request {
|
|
return httptest.NewRequest(method, path, strings.NewReader(body)).WithContext(auth.WithPrincipal(context.Background(), auth.Principal{Subject: "operator-1", Role: role}))
|
|
}
|
|
|
|
func TestIncidentHandlerRBACAndManualAssociationAudit(t *testing.T) {
|
|
store := &memoryStore{item: incident.Incident{ID: "incident-1", Title: "Host down", Status: incident.StatusOpen, Severity: incident.SeverityCritical, Revision: 1}}
|
|
auditStore := &audit.MemoryStore{}
|
|
handler := Handler{Store: store, Audit: auditStore}
|
|
request := requestWithPrincipal(http.MethodPost, "/api/v1/incidents/incident-1/alerts/alert-1", `{"rationale":"confirmed dependency","confidence":1}`, auth.RoleViewer)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
if response.Code != http.StatusForbidden {
|
|
t.Fatalf("viewer status=%d", response.Code)
|
|
}
|
|
request = requestWithPrincipal(http.MethodPost, "/api/v1/incidents/incident-1/alerts/alert-1", `{"rationale":"confirmed dependency","confidence":1}`, auth.RoleOperator)
|
|
response = httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), `"manual":true`) {
|
|
t.Fatalf("association status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
if len(auditStore.Events) != 1 || auditStore.Events[0].Action != "incident.alert.associate" {
|
|
t.Fatalf("audit=%+v", auditStore.Events)
|
|
}
|
|
request = requestWithPrincipal(http.MethodGet, "/api/v1/incidents?status=open", ``, auth.RoleViewer)
|
|
response = httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("list status=%d", response.Code)
|
|
}
|
|
}
|
|
func (s *memoryStore) UpdateOwner(_ context.Context, _ string, owner string, _ int64) (incident.Incident, error) {
|
|
s.item.OwnerUserID = owner
|
|
s.item.Revision++
|
|
return s.item, nil
|
|
}
|
|
func (s *memoryStore) AddNote(_ context.Context, id, author, body string) (incident.Note, error) {
|
|
sanitized, err := incident.SanitizeNote(body)
|
|
if err != nil {
|
|
return incident.Note{}, err
|
|
}
|
|
return incident.Note{ID: "note-1", IncidentID: id, Author: author, Body: sanitized, CreatedAt: time.Now().UTC()}, nil
|
|
}
|
|
func (s *memoryStore) ListNotes(context.Context, string, int) ([]incident.Note, error) {
|
|
return nil, nil
|
|
}
|
|
func TestIncidentHandlerNotesAndOwner(t *testing.T) {
|
|
store := &memoryStore{item: incident.Incident{ID: "incident-1", Title: "Host down", Status: incident.StatusOpen, Severity: incident.SeverityCritical, Revision: 1}}
|
|
handler := Handler{Store: store, Audit: &audit.MemoryStore{}}
|
|
request := requestWithPrincipal(http.MethodPost, "/api/v1/incidents/incident-1/notes", `{"body":"<b>confirmed</b>"}`, auth.RoleViewer)
|
|
response := httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
if response.Code != http.StatusForbidden {
|
|
t.Fatalf("viewer note status=%d", response.Code)
|
|
}
|
|
request = requestWithPrincipal(http.MethodPost, "/api/v1/incidents/incident-1/notes", `{"body":"<b>confirmed</b>"}`, auth.RoleOperator)
|
|
response = httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
if response.Code != http.StatusCreated || strings.Contains(response.Body.String(), "<b>") || !strings.Contains(response.Body.String(), "confirmed") {
|
|
t.Fatalf("note status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
request = requestWithPrincipal(http.MethodPatch, "/api/v1/incidents/incident-1", `{"ownerUserId":"owner-1","revision":1}`, auth.RoleOperator)
|
|
response = httptest.NewRecorder()
|
|
handler.ServeHTTP(response, request)
|
|
if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), "owner-1") {
|
|
t.Fatalf("owner status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
}
|