server/internal/handlers/incidents_test.go
Rama main · réplica de lectura del repositorio original.
package handlers_test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"blackbox/server/internal/handlers"
"blackbox/server/internal/models"
"blackbox/shared/types"
"github.com/go-chi/chi/v5"
"github.com/oklog/ulid/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestListIncidents(t *testing.T) {
database := newTestDB(t)
now := time.Now().UTC()
inc := models.Incident{
ID: ulid.Make().String(),
OpenedAt: now,
Status: "open",
Confidence: "confirmed",
Title: "nginx down",
Services: `["nginx"]`,
NodeNames: `["node-01"]`,
Metadata: `{}`,
}
require.NoError(t, database.Create(&inc).Error)
req := httptest.NewRequest(http.MethodGet, "/api/incidents", nil)
rr := httptest.NewRecorder()
handlers.ListIncidents(database)(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
var resp struct {
Incidents []models.Incident `json:"incidents"`
}
require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
assert.Len(t, resp.Incidents, 1)
assert.Equal(t, inc.ID, resp.Incidents[0].ID)
}
func TestListIncidents_PaginatesWithHasMore(t *testing.T) {
database := newTestDB(t)
now := time.Now().UTC()
older := models.Incident{
ID: ulid.Make().String(),
OpenedAt: now,
Status: "open",
Confidence: "confirmed",
Title: "older incident",
Services: `["nginx"]`,
NodeNames: `["node-01"]`,
Metadata: `{}`,
}
newer := models.Incident{
ID: ulid.Make().String(),
OpenedAt: now.Add(time.Second),
Status: "open",
Confidence: "confirmed",
Title: "newer incident",
Services: `["traefik"]`,
NodeNames: `["node-02"]`,
Metadata: `{}`,
}
require.NoError(t, database.Create(&older).Error)
require.NoError(t, database.Create(&newer).Error)
req := httptest.NewRequest(http.MethodGet, "/api/incidents?limit=1", nil)
rr := httptest.NewRecorder()
handlers.ListIncidents(database)(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
var resp struct {
Incidents []models.Incident `json:"incidents"`
HasMore bool `json:"has_more"`
}
require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
assert.Len(t, resp.Incidents, 1)
assert.Equal(t, newer.ID, resp.Incidents[0].ID)
assert.True(t, resp.HasMore)
}
func TestGetIncidentSummary(t *testing.T) {
database := newTestDB(t)
require.NoError(t, database.Create(&models.Incident{
ID: ulid.Make().String(),
OpenedAt: time.Now().UTC().Add(time.Minute),
Status: "open",
Confidence: "confirmed",
Title: "confirmed open incident",
Services: `["nginx"]`,
NodeNames: `["node-01"]`,
Metadata: `{}`,
}).Error)
require.NoError(t, database.Create(&models.Incident{
ID: ulid.Make().String(),
OpenedAt: time.Now().UTC(),
Status: "open",
Confidence: "suspected",
Title: "suspected open incident",
Services: `["traefik"]`,
NodeNames: `["node-02"]`,
Metadata: `{}`,
}).Error)
require.NoError(t, database.Create(&models.Incident{
ID: ulid.Make().String(),
OpenedAt: time.Now().UTC().Add(-time.Minute),
Status: "resolved",
Confidence: "confirmed",
Title: "resolved incident",
Services: `["redis"]`,
NodeNames: `["node-03"]`,
Metadata: `{}`,
}).Error)
req := httptest.NewRequest(http.MethodGet, "/api/incidents/summary", nil)
rr := httptest.NewRecorder()
handlers.GetIncidentSummary(database)(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
var resp struct {
OpenCount int64 `json:"open_count"`
HasConfirmedOpen bool `json:"has_confirmed_open"`
}
require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
assert.EqualValues(t, 2, resp.OpenCount)
assert.True(t, resp.HasConfirmedOpen)
assert.Equal(t, "no-store", rr.Header().Get("Cache-Control"))
}
func TestGetIncident_NotFound(t *testing.T) {
database := newTestDB(t)
r := chi.NewRouter()
r.Get("/api/incidents/{id}", handlers.GetIncident(database))
req := httptest.NewRequest(http.MethodGet, "/api/incidents/nonexistent", nil)
rr := httptest.NewRecorder()
r.ServeHTTP(rr, req)
assert.Equal(t, http.StatusNotFound, rr.Code)
}
func TestGetIncident_Found(t *testing.T) {
database := newTestDB(t)
incident := models.Incident{
ID: ulid.Make().String(),
OpenedAt: time.Now().UTC(),
Status: "open",
Confidence: "confirmed",
Title: "traefik down",
Services: `["traefik"]`,
NodeNames: `["node-01"]`,
Metadata: `{}`,
}
require.NoError(t, database.Create(&incident).Error)
r := chi.NewRouter()
r.Get("/api/incidents/{id}", handlers.GetIncident(database))
req := httptest.NewRequest(http.MethodGet, "/api/incidents/"+incident.ID, nil)
rr := httptest.NewRecorder()
r.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
var resp struct {
Incident models.Incident `json:"incident"`
}
require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
assert.Equal(t, incident.ID, resp.Incident.ID)
assert.Equal(t, incident.Title, resp.Incident.Title)
}
func TestListIncidentMembership(t *testing.T) {
database := newTestDB(t)
entryA := types.Entry{ID: ulid.Make().String(), Timestamp: time.Now().UTC()}
entryB := types.Entry{ID: ulid.Make().String(), Timestamp: time.Now().UTC()}
require.NoError(t, database.Create(&entryA).Error)
require.NoError(t, database.Create(&entryB).Error)
openIncident := models.Incident{
ID: ulid.Make().String(),
OpenedAt: time.Now().UTC().Add(time.Minute),
Status: "open",
Confidence: "confirmed",
Title: "open incident",
Services: `["svc"]`,
NodeNames: `["node-01"]`,
Metadata: `{}`,
}
resolvedIncident := models.Incident{
ID: ulid.Make().String(),
OpenedAt: time.Now().UTC(),
Status: "resolved",
Confidence: "suspected",
Title: "resolved incident",
Services: `["svc"]`,
NodeNames: `["node-01"]`,
Metadata: `{}`,
}
require.NoError(t, database.Create(&openIncident).Error)
require.NoError(t, database.Create(&resolvedIncident).Error)
require.NoError(t, database.Create(&models.IncidentEntry{
IncidentID: openIncident.ID,
EntryID: entryA.ID,
Role: "cause",
Score: 90,
}).Error)
require.NoError(t, database.Create(&models.IncidentEntry{
IncidentID: resolvedIncident.ID,
EntryID: entryB.ID,
Role: "cause",
Score: 80,
}).Error)
body := bytes.NewBufferString(`{"entry_ids":["` + entryA.ID + `","` + entryB.ID + `","` + entryA.ID + `"]}`)
req := httptest.NewRequest(http.MethodPost, "/api/incidents/membership", body)
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handlers.ListIncidentMembership(database)(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
var resp struct {
Memberships map[string]struct {
ID string `json:"id"`
Confidence string `json:"confidence"`
} `json:"memberships"`
}
require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
require.Len(t, resp.Memberships, 1)
assert.Equal(t, openIncident.ID, resp.Memberships[entryA.ID].ID)
assert.Equal(t, openIncident.Confidence, resp.Memberships[entryA.ID].Confidence)
}
func TestListIncidents_EscapesServiceWildcards(t *testing.T) {
database := newTestDB(t)
require.NoError(t, database.Create(&models.Incident{
ID: ulid.Make().String(),
OpenedAt: time.Now().UTC(),
Status: "open",
Confidence: "confirmed",
Title: "svc_1 down",
Services: `["svc_1"]`,
NodeNames: `["node-01"]`,
Metadata: `{}`,
}).Error)
require.NoError(t, database.Create(&models.Incident{
ID: ulid.Make().String(),
OpenedAt: time.Now().UTC(),
Status: "open",
Confidence: "confirmed",
Title: "svcA1 down",
Services: `["svcA1"]`,
NodeNames: `["node-02"]`,
Metadata: `{}`,
}).Error)
req := httptest.NewRequest(http.MethodGet, "/api/incidents?service=svc_1", nil)
rr := httptest.NewRecorder()
handlers.ListIncidents(database)(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
var resp struct {
Incidents []models.Incident `json:"incidents"`
}
require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
require.Len(t, resp.Incidents, 1)
assert.Equal(t, `["svc_1"]`, resp.Incidents[0].Services)
}