test: initialise and stop hub fixture workers before cleanup (#2463)

This commit is contained in:
user01010111
2026-09-29 12:42:36 +13:00
committed by GitHub
parent 130365f5d3
commit 8b38db1ba0
14 changed files with 292 additions and 110 deletions

View File

@@ -5,6 +5,7 @@ package alerts_test
import (
"encoding/json"
"testing"
"testing/synctest"
"time"
"github.com/henrygd/beszel/internal/entities/system"
@@ -23,7 +24,7 @@ func TestBatteryAlertLogic(t *testing.T) {
defer hub.Cleanup()
// Create a system
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "paused")
require.NoError(t, err)
systemRecord := systems[0]
@@ -68,13 +69,12 @@ func TestBatteryAlertLogic(t *testing.T) {
// Simulate system update time
systemRecord.Set("updated", time.Now().UTC())
err = hub.SaveNoValidate(systemRecord)
require.NoError(t, err)
// Handle system alerts with high battery
am := hub.GetAlertManager()
err = am.HandleSystemAlerts(systemRecord, combinedDataHigh)
require.NoError(t, err)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, am.HandleSystemAlerts(systemRecord, combinedDataHigh))
})
// Verify alert is still NOT triggered (battery 50% is above threshold 20%)
batteryAlert, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": batteryAlert.Id})
@@ -108,15 +108,11 @@ func TestBatteryAlertLogic(t *testing.T) {
// Update system timestamp
systemRecord.Set("updated", time.Now().UTC())
err = hub.SaveNoValidate(systemRecord)
require.NoError(t, err)
// Handle system alerts with low battery
err = am.HandleSystemAlerts(systemRecord, combinedDataLow)
require.NoError(t, err)
// Wait for the alert to be processed
time.Sleep(20 * time.Millisecond)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, am.HandleSystemAlerts(systemRecord, combinedDataLow))
})
// Verify alert IS triggered (battery 15% is below threshold 20%)
batteryAlert, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": batteryAlert.Id})
@@ -150,15 +146,11 @@ func TestBatteryAlertLogic(t *testing.T) {
// Update system timestamp
systemRecord.Set("updated", time.Now().UTC())
err = hub.SaveNoValidate(systemRecord)
require.NoError(t, err)
// Handle system alerts with recovered battery
err = am.HandleSystemAlerts(systemRecord, combinedDataRecovered)
require.NoError(t, err)
// Wait for the alert to be processed
time.Sleep(20 * time.Millisecond)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, am.HandleSystemAlerts(systemRecord, combinedDataRecovered))
})
// Verify alert is now resolved (battery 25% is above threshold 20%)
batteryAlert, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": batteryAlert.Id})
@@ -172,7 +164,7 @@ func TestBatteryAlertNoBattery(t *testing.T) {
defer hub.Cleanup()
// Create a system
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "paused")
require.NoError(t, err)
systemRecord := systems[0]
@@ -206,16 +198,12 @@ func TestBatteryAlertNoBattery(t *testing.T) {
// Simulate system update time
systemRecord.Set("updated", time.Now().UTC())
err = hub.SaveNoValidate(systemRecord)
require.NoError(t, err)
// Handle system alerts
am := hub.GetAlertManager()
err = am.HandleSystemAlerts(systemRecord, combinedData)
require.NoError(t, err)
// Wait a moment for processing
time.Sleep(20 * time.Millisecond)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, am.HandleSystemAlerts(systemRecord, combinedData))
})
// Verify alert is NOT triggered (no battery data should skip the alert)
batteryAlert, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": batteryAlert.Id})
@@ -230,7 +218,7 @@ func TestBatteryAlertAveragedSamples(t *testing.T) {
defer hub.Cleanup()
// Create a system
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "paused")
require.NoError(t, err)
systemRecord := systems[0]
@@ -302,15 +290,11 @@ func TestBatteryAlertAveragedSamples(t *testing.T) {
// Update system timestamp
systemRecord.Set("updated", now)
err = hub.SaveNoValidate(systemRecord)
require.NoError(t, err)
// Handle system alerts - should trigger because average battery is below threshold
err = am.HandleSystemAlerts(systemRecord, combinedDataLow)
require.NoError(t, err)
// Wait for alert processing
time.Sleep(20 * time.Millisecond)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, am.HandleSystemAlerts(systemRecord, combinedDataLow))
})
// Verify alert IS triggered (average battery 15% is below threshold 25%)
batteryAlert, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": batteryAlert.Id})
@@ -368,15 +352,11 @@ func TestBatteryAlertAveragedSamples(t *testing.T) {
// Update system timestamp to the new time window
systemRecord.Set("updated", newNow)
err = hub.SaveNoValidate(systemRecord)
require.NoError(t, err)
// Handle system alerts - should resolve because average battery is now above threshold
err = am.HandleSystemAlerts(systemRecord, combinedDataHigh)
require.NoError(t, err)
// Wait for alert processing
time.Sleep(20 * time.Millisecond)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, am.HandleSystemAlerts(systemRecord, combinedDataHigh))
})
// Verify alert is resolved (average battery 50% is above threshold 25%)
batteryAlert, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": batteryAlert.Id})

View File

@@ -5,6 +5,7 @@ package alerts_test
import (
"encoding/json"
"testing"
"testing/synctest"
"time"
"github.com/henrygd/beszel/internal/entities/system"
@@ -22,7 +23,7 @@ func TestDiskAlertExtraFsMultiMinute(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "paused")
require.NoError(t, err)
systemRecord := systems[0]
@@ -83,13 +84,10 @@ func TestDiskAlertExtraFsMultiMinute(t *testing.T) {
}
systemRecord.Set("updated", now)
err = hub.SaveNoValidate(systemRecord)
require.NoError(t, err)
err = am.HandleSystemAlerts(systemRecord, combinedDataHigh)
require.NoError(t, err)
time.Sleep(20 * time.Millisecond)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, am.HandleSystemAlerts(systemRecord, combinedDataHigh))
})
diskAlert, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": diskAlert.Id})
require.NoError(t, err)
@@ -140,13 +138,10 @@ func TestDiskAlertExtraFsMultiMinute(t *testing.T) {
}
systemRecord.Set("updated", newNow)
err = hub.SaveNoValidate(systemRecord)
require.NoError(t, err)
err = am.HandleSystemAlerts(systemRecord, combinedDataLow)
require.NoError(t, err)
time.Sleep(20 * time.Millisecond)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, am.HandleSystemAlerts(systemRecord, combinedDataLow))
})
diskAlert, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": diskAlert.Id})
require.NoError(t, err)

View File

@@ -323,9 +323,14 @@ func TestAlertSilencedMultiUser(t *testing.T) {
func TestAlertSilencedWithActualAlert(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
synctest.Test(t, func(t *testing.T) {
defer hub.Cleanup()
t.Cleanup(func() {
synctest.Wait()
hub.GetAlertManager().Stop()
})
hub.GetSystemManager().ResetContextForTesting(t)
// Create a system
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")

View File

@@ -0,0 +1,48 @@
//go:build testing
package alerts_test
import (
"sync"
"testing"
"github.com/henrygd/beszel/internal/alerts"
beszelTests "github.com/henrygd/beszel/internal/tests"
"github.com/stretchr/testify/require"
)
func TestStandaloneAlertManagerStopsBeforeDatabaseCleanup(t *testing.T) {
for _, state := range []string{"pending", "already stopped", "delivered"} {
t.Run(state, func(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
cleanup := sync.OnceFunc(hub.Cleanup)
defer cleanup()
setStatusAlertEmail(t, hub, user.Id, "shutdown@example.com")
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "paused")
require.NoError(t, err)
_, err = beszelTests.CreateRecord(hub, "alerts", map[string]any{
"name": "Status", "system": systems[0].Id, "user": user.Id, "min": 1,
})
require.NoError(t, err)
am := alerts.NewTestAlertManagerWithoutWorker(hub)
defer am.Stop()
require.NoError(t, am.HandleStatusAlerts("down", systems[0]))
require.Equal(t, 1, am.GetPendingAlertsCount())
switch state {
case "already stopped":
am.Stop()
case "delivered":
am.ForceExpirePendingAlerts()
processed, err := am.ProcessPendingAlerts()
require.NoError(t, err)
require.Len(t, processed, 1)
require.Equal(t, 1, hub.TestMailer.TotalSend())
}
cleanup()
// No timer may retain the disposed app until its eventual deadline.
require.Zero(t, am.GetPendingAlertsCount())
})
}
}

View File

@@ -30,9 +30,14 @@ func setStatusAlertEmail(t *testing.T, hub core.App, userID, email string) {
func TestStatusAlerts(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
synctest.Test(t, func(t *testing.T) {
defer hub.Cleanup()
t.Cleanup(func() {
synctest.Wait()
hub.GetAlertManager().Stop()
})
hub.GetSystemManager().ResetContextForTesting(t)
systems, err := beszelTests.CreateSystems(hub, 4, user.Id, "paused")
assert.NoError(t, err)
@@ -236,9 +241,14 @@ func TestHandleStatusAlertsDoesNotSendRecoveryWhileDownIsOnlyPending(t *testing.
func TestStatusAlertTimerCancellationPreventsBoundaryDelivery(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
synctest.Test(t, func(t *testing.T) {
defer hub.Cleanup()
t.Cleanup(func() {
synctest.Wait()
hub.GetAlertManager().Stop()
})
hub.GetSystemManager().ResetContextForTesting(t)
userSettings, err := hub.FindFirstRecordByFilter("user_settings", "user={:user}", map[string]any{"user": user.Id})
require.NoError(t, err)
@@ -339,9 +349,14 @@ func TestStatusAlertDownFiresAfterDelayExpires(t *testing.T) {
func TestStatusAlertMultipleUsersRespectDifferentMinutes(t *testing.T) {
hub, user1 := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
synctest.Test(t, func(t *testing.T) {
defer hub.Cleanup()
t.Cleanup(func() {
synctest.Wait()
hub.GetAlertManager().Stop()
})
hub.GetSystemManager().ResetContextForTesting(t)
setStatusAlertEmail(t, hub, user1.Id, "user1@example.com")
@@ -427,9 +442,14 @@ func TestStatusAlertMultipleUsersRespectDifferentMinutes(t *testing.T) {
func TestStatusAlertMultipleUsersRecoveryBetweenMinutesOnlyAlertsEarlierUser(t *testing.T) {
hub, user1 := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
synctest.Test(t, func(t *testing.T) {
defer hub.Cleanup()
t.Cleanup(func() {
synctest.Wait()
hub.GetAlertManager().Stop()
})
hub.GetSystemManager().ResetContextForTesting(t)
setStatusAlertEmail(t, hub, user1.Id, "user1@example.com")
@@ -821,9 +841,14 @@ func TestResolveStatusAlerts(t *testing.T) {
func TestAlertsHistoryStatus(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
synctest.Test(t, func(t *testing.T) {
defer hub.Cleanup()
t.Cleanup(func() {
synctest.Wait()
hub.GetAlertManager().Stop()
})
hub.GetSystemManager().ResetContextForTesting(t)
// Create a system
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
@@ -888,9 +913,14 @@ func TestAlertsHistoryStatus(t *testing.T) {
func TestStatusAlertClearedBeforeSend(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
synctest.Test(t, func(t *testing.T) {
defer hub.Cleanup()
t.Cleanup(func() {
synctest.Wait()
hub.GetAlertManager().Stop()
})
hub.GetSystemManager().ResetContextForTesting(t)
// Create a system
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")

View File

@@ -16,9 +16,14 @@ import (
func TestAlertsHistory(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
synctest.Test(t, func(t *testing.T) {
defer hub.Cleanup()
t.Cleanup(func() {
synctest.Wait()
hub.GetAlertManager().Stop()
})
hub.GetSystemManager().ResetContextForTesting(t)
// Create systems and alerts
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")

View File

@@ -10,11 +10,17 @@ import (
)
func NewTestAlertManagerWithoutWorker(app hubLike) *AlertManager {
return &AlertManager{
am := &AlertManager{
hub: app,
alertsCache: NewAlertsCache(app),
networkMonitors: newNetworkMonitorCache(app),
}
// Standalone managers can own status timers even without the serve hooks.
app.OnTerminate().BindFunc(func(e *core.TerminateEvent) error {
am.Stop()
return e.Next()
})
return am
}
// GetSystemAlertsCache returns the internal system alerts cache.

View File

@@ -5,6 +5,7 @@ package alerts_test
import (
"encoding/json"
"testing"
"testing/synctest"
"time"
"github.com/henrygd/beszel/internal/entities/system"
@@ -23,7 +24,7 @@ func TestDiskAlertZfsPoolMultiMinute(t *testing.T) {
hub, user := beszelTests.GetHubWithUser(t)
defer hub.Cleanup()
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "paused")
require.NoError(t, err)
systemRecord := systems[0]
@@ -80,13 +81,10 @@ func TestDiskAlertZfsPoolMultiMinute(t *testing.T) {
}
systemRecord.Set("updated", now)
err = hub.SaveNoValidate(systemRecord)
require.NoError(t, err)
err = am.HandleSystemAlerts(systemRecord, combinedDataHigh)
require.NoError(t, err)
time.Sleep(20 * time.Millisecond)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, am.HandleSystemAlerts(systemRecord, combinedDataHigh))
})
diskAlert, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": diskAlert.Id})
require.NoError(t, err)
@@ -130,13 +128,10 @@ func TestDiskAlertZfsPoolMultiMinute(t *testing.T) {
}
systemRecord.Set("updated", newNow)
err = hub.SaveNoValidate(systemRecord)
require.NoError(t, err)
err = am.HandleSystemAlerts(systemRecord, combinedDataLow)
require.NoError(t, err)
time.Sleep(20 * time.Millisecond)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, am.HandleSystemAlerts(systemRecord, combinedDataLow))
})
diskAlert, err = hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": diskAlert.Id})
require.NoError(t, err)
@@ -147,7 +142,7 @@ func TestDiskAlertZfsPoolMultiMinute(t *testing.T) {
func TestDiskAlertIgnoresRawPool(t *testing.T) {
for _, minutes := range []int{0, 2} {
hub, user := beszelTests.GetHubWithUser(t)
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "paused")
require.NoError(t, err)
alert, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{"name": "Disk", "system": systems[0].Id, "user": user.Id, "value": 80, "min": minutes})
require.NoError(t, err)
@@ -160,16 +155,18 @@ func TestDiskAlertIgnoresRawPool(t *testing.T) {
record.SetRaw("created", time.Now().UTC().Add(offset*time.Second).Format(types.DefaultDateLayout))
require.NoError(t, hub.SaveNoValidate(record))
}
require.NoError(t, hub.GetAlertManager().HandleSystemAlerts(systems[0], &system.CombinedData{Stats: system.Stats{ZfsPools: pools}}))
time.Sleep(20 * time.Millisecond)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, hub.GetAlertManager().HandleSystemAlerts(systems[0], &system.CombinedData{Stats: system.Stats{ZfsPools: pools}}))
})
record, err := hub.FindRecordById("alerts", alert.Id)
require.NoError(t, err)
assert.False(t, record.GetBool("triggered"))
if minutes > 0 {
// A current usable sample must not make raw historical values eligible.
pools["btrfs"].Raw = false
require.NoError(t, hub.GetAlertManager().HandleSystemAlerts(systems[0], &system.CombinedData{Stats: system.Stats{ZfsPools: pools}}))
time.Sleep(20 * time.Millisecond)
synctest.Test(t, func(t *testing.T) {
require.NoError(t, hub.GetAlertManager().HandleSystemAlerts(systems[0], &system.CombinedData{Stats: system.Stats{ZfsPools: pools}}))
})
record, err = hub.FindRecordById("alerts", alert.Id)
require.NoError(t, err)
assert.False(t, record.GetBool("triggered"))