mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-22 05:36:15 +01:00
258 lines
7.3 KiB
Go
258 lines
7.3 KiB
Go
//go:build testing
|
|
|
|
package heartbeat_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/henrygd/beszel/internal/hub/heartbeat"
|
|
beszeltests "github.com/henrygd/beszel/internal/tests"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNew(t *testing.T) {
|
|
t.Run("returns nil when app is missing", func(t *testing.T) {
|
|
hb := heartbeat.New(nil, envGetter(map[string]string{
|
|
"HEARTBEAT_URL": "https://heartbeat.example.com/ping",
|
|
}))
|
|
assert.Nil(t, hb)
|
|
})
|
|
|
|
t.Run("returns nil when URL is missing", func(t *testing.T) {
|
|
app := newTestHub(t)
|
|
hb := heartbeat.New(app.App, func(string) (string, bool) {
|
|
return "", false
|
|
})
|
|
assert.Nil(t, hb)
|
|
})
|
|
|
|
t.Run("parses and normalizes config values", func(t *testing.T) {
|
|
app := newTestHub(t)
|
|
env := map[string]string{
|
|
"HEARTBEAT_URL": " https://heartbeat.example.com/ping ",
|
|
"HEARTBEAT_INTERVAL": "90",
|
|
"HEARTBEAT_METHOD": "head",
|
|
}
|
|
getEnv := func(key string) (string, bool) {
|
|
v, ok := env[key]
|
|
return v, ok
|
|
}
|
|
|
|
hb := heartbeat.New(app.App, getEnv)
|
|
require.NotNil(t, hb)
|
|
cfg := hb.GetConfig()
|
|
assert.Equal(t, "https://heartbeat.example.com/ping", cfg.URL)
|
|
assert.Equal(t, 90, cfg.Interval)
|
|
assert.Equal(t, http.MethodHead, cfg.Method)
|
|
})
|
|
}
|
|
|
|
func TestSendGETDoesNotRequireAppOrDB(t *testing.T) {
|
|
app := newTestHub(t)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, http.MethodGet, r.Method)
|
|
assert.Equal(t, "Beszel-Heartbeat", r.Header.Get("User-Agent"))
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
hb := heartbeat.New(app.App, envGetter(map[string]string{
|
|
"HEARTBEAT_URL": server.URL,
|
|
"HEARTBEAT_METHOD": "GET",
|
|
}))
|
|
require.NotNil(t, hb)
|
|
|
|
require.NoError(t, hb.Send())
|
|
}
|
|
|
|
func TestSendReturnsErrorOnHTTPFailureStatus(t *testing.T) {
|
|
app := newTestHub(t)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer server.Close()
|
|
|
|
hb := heartbeat.New(app.App, envGetter(map[string]string{
|
|
"HEARTBEAT_URL": server.URL,
|
|
"HEARTBEAT_METHOD": "GET",
|
|
}))
|
|
require.NotNil(t, hb)
|
|
|
|
err := hb.Send()
|
|
require.Error(t, err)
|
|
assert.ErrorContains(t, err, "heartbeat endpoint returned status 500")
|
|
}
|
|
|
|
func TestSendPOSTBuildsExpectedStatuses(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(t *testing.T, app *beszeltests.TestHub, user *core.Record)
|
|
expectStatus string
|
|
expectMsgPart string
|
|
expectDown int
|
|
expectAlerts int
|
|
expectTotal int
|
|
expectUp int
|
|
expectPaused int
|
|
expectPending int
|
|
expectDownSumm int
|
|
}{
|
|
{
|
|
name: "error when at least one system is down",
|
|
setup: func(t *testing.T, app *beszeltests.TestHub, user *core.Record) {
|
|
downSystem := createTestSystem(t, app, user.Id, "db-1", "10.0.0.1", "down")
|
|
_ = createTestSystem(t, app, user.Id, "web-1", "10.0.0.2", "up")
|
|
createTriggeredAlert(t, app, user.Id, downSystem.Id, "CPU", 95)
|
|
},
|
|
expectStatus: "error",
|
|
expectMsgPart: "1 system(s) down",
|
|
expectDown: 1,
|
|
expectAlerts: 1,
|
|
expectTotal: 2,
|
|
expectUp: 1,
|
|
expectDownSumm: 1,
|
|
},
|
|
{
|
|
name: "warn when only alerts are triggered",
|
|
setup: func(t *testing.T, app *beszeltests.TestHub, user *core.Record) {
|
|
system := createTestSystem(t, app, user.Id, "api-1", "10.1.0.1", "up")
|
|
createTriggeredAlert(t, app, user.Id, system.Id, "CPU", 90)
|
|
},
|
|
expectStatus: "warn",
|
|
expectMsgPart: "1 alert(s) triggered",
|
|
expectDown: 0,
|
|
expectAlerts: 1,
|
|
expectTotal: 1,
|
|
expectUp: 1,
|
|
expectDownSumm: 0,
|
|
},
|
|
{
|
|
name: "ok when no down systems and no alerts",
|
|
setup: func(t *testing.T, app *beszeltests.TestHub, user *core.Record) {
|
|
_ = createTestSystem(t, app, user.Id, "node-1", "10.2.0.1", "up")
|
|
_ = createTestSystem(t, app, user.Id, "node-2", "10.2.0.2", "paused")
|
|
_ = createTestSystem(t, app, user.Id, "node-3", "10.2.0.3", "pending")
|
|
},
|
|
expectStatus: "ok",
|
|
expectMsgPart: "All systems operational",
|
|
expectDown: 0,
|
|
expectAlerts: 0,
|
|
expectTotal: 3,
|
|
expectUp: 1,
|
|
expectPaused: 1,
|
|
expectPending: 1,
|
|
expectDownSumm: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
app := newTestHub(t)
|
|
user := createTestUser(t, app)
|
|
tt.setup(t, app, user)
|
|
|
|
type requestCapture struct {
|
|
method string
|
|
userAgent string
|
|
contentType string
|
|
payload heartbeat.Payload
|
|
}
|
|
|
|
captured := make(chan requestCapture, 1)
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close()
|
|
body, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
|
|
var payload heartbeat.Payload
|
|
require.NoError(t, json.Unmarshal(body, &payload))
|
|
captured <- requestCapture{
|
|
method: r.Method,
|
|
userAgent: r.Header.Get("User-Agent"),
|
|
contentType: r.Header.Get("Content-Type"),
|
|
payload: payload,
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}))
|
|
defer server.Close()
|
|
|
|
hb := heartbeat.New(app.App, envGetter(map[string]string{
|
|
"HEARTBEAT_URL": server.URL,
|
|
"HEARTBEAT_METHOD": "POST",
|
|
}))
|
|
require.NotNil(t, hb)
|
|
require.NoError(t, hb.Send())
|
|
|
|
req := <-captured
|
|
assert.Equal(t, http.MethodPost, req.method)
|
|
assert.Equal(t, "Beszel-Heartbeat", req.userAgent)
|
|
assert.Equal(t, "application/json", req.contentType)
|
|
|
|
assert.Equal(t, tt.expectStatus, req.payload.Status)
|
|
assert.Contains(t, req.payload.Msg, tt.expectMsgPart)
|
|
assert.Equal(t, tt.expectDown, len(req.payload.Down))
|
|
assert.Equal(t, tt.expectAlerts, len(req.payload.Alerts))
|
|
assert.Equal(t, tt.expectTotal, req.payload.Systems.Total)
|
|
assert.Equal(t, tt.expectUp, req.payload.Systems.Up)
|
|
assert.Equal(t, tt.expectDownSumm, req.payload.Systems.Down)
|
|
assert.Equal(t, tt.expectPaused, req.payload.Systems.Paused)
|
|
assert.Equal(t, tt.expectPending, req.payload.Systems.Pending)
|
|
})
|
|
}
|
|
}
|
|
|
|
func newTestHub(t *testing.T) *beszeltests.TestHub {
|
|
t.Helper()
|
|
app, err := beszeltests.NewTestHub(t.TempDir())
|
|
require.NoError(t, err)
|
|
t.Cleanup(app.Cleanup)
|
|
return app
|
|
}
|
|
|
|
func createTestUser(t *testing.T, app *beszeltests.TestHub) *core.Record {
|
|
t.Helper()
|
|
user, err := beszeltests.CreateUser(app.App, "admin@example.com", "password123")
|
|
require.NoError(t, err)
|
|
return user
|
|
}
|
|
|
|
func createTestSystem(t *testing.T, app *beszeltests.TestHub, userID, name, host, status string) *core.Record {
|
|
t.Helper()
|
|
system, err := beszeltests.CreateRecord(app.App, "systems", map[string]any{
|
|
"name": name,
|
|
"host": host,
|
|
"port": "45876",
|
|
"users": []string{userID},
|
|
"status": status,
|
|
})
|
|
require.NoError(t, err)
|
|
return system
|
|
}
|
|
|
|
func createTriggeredAlert(t *testing.T, app *beszeltests.TestHub, userID, systemID, name string, threshold float64) *core.Record {
|
|
t.Helper()
|
|
alert, err := beszeltests.CreateRecord(app.App, "alerts", map[string]any{
|
|
"name": name,
|
|
"system": systemID,
|
|
"user": userID,
|
|
"value": threshold,
|
|
"min": 0,
|
|
"triggered": true,
|
|
})
|
|
require.NoError(t, err)
|
|
return alert
|
|
}
|
|
|
|
func envGetter(values map[string]string) func(string) (string, bool) {
|
|
return func(key string) (string, bool) {
|
|
v, ok := values[key]
|
|
return v, ok
|
|
}
|
|
}
|