Ctrl K
ComunidadGuías…

server/internal/handlers/incidents_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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)
}