Fix backend test failures blocking release workflow

Three categories of fixes:

1. Goroutine leak causing 10-minute timeout:
   - Add defer mon.notificationMgr.Stop() in monitor_memory_test.go
   - Background goroutines from notification manager weren't being stopped

2. Database NULL column scanning errors:
   - Change LastError from string to *string in queue.go
   - Change PayloadBytes from int to *int in queue.go
   - SQL NULL values require pointer types in Go

3. SSRF protection blocking test servers:
   - Check allowlist for localhost before rejecting in notifications.go
   - Set PULSE_DATA_DIR to temp directory in tests
   - Add defer nm.Stop() calls to prevent goroutine leaks

Fixes for preflight test failures in workflow run 19280879903.
This commit is contained in:
rcourtman 2025-11-11 23:27:03 +00:00
parent 92a5d74ba9
commit b41f8a2ac4
4 changed files with 19 additions and 4 deletions

View file

@ -193,6 +193,7 @@ func TestPollPVEInstanceUsesRRDMemUsedFallback(t *testing.T) {
lastAuthAttempt: make(map[string]time.Time),
}
defer mon.alertManager.Stop()
defer mon.notificationMgr.Stop()
mon.pollPVEInstance(context.Background(), "test", client)

View file

@ -1999,9 +1999,17 @@ func (n *NotificationManager) ValidateWebhookURL(webhookURL string) error {
return fmt.Errorf("webhook URL missing hostname")
}
// Block localhost and loopback addresses (SSRF protection)
// Block localhost and loopback addresses (SSRF protection) unless allowlisted
if host == "localhost" || host == "127.0.0.1" || host == "::1" || strings.HasPrefix(host, "127.") {
return fmt.Errorf("webhook URLs pointing to localhost are not allowed for security reasons")
// Check if localhost is in the allowlist
localhostIP := net.ParseIP("127.0.0.1")
if !n.isIPInAllowlist(localhostIP) {
return fmt.Errorf("webhook URLs pointing to localhost are not allowed for security reasons")
}
log.Debug().
Str("host", host).
Str("url", webhookURL).
Msg("Localhost webhook URL allowed via allowlist")
}
// Block link-local addresses

View file

@ -199,7 +199,10 @@ func TestSendGroupedAppriseInvokesExecutor(t *testing.T) {
}
func TestSendGroupedAppriseHTTP(t *testing.T) {
t.Setenv("PULSE_DATA_DIR", t.TempDir())
nm := NewNotificationManager("https://pulse.local")
defer nm.Stop()
nm.SetGroupingWindow(0)
nm.SetEmailConfig(EmailConfig{Enabled: false})
@ -563,7 +566,10 @@ func TestSendTestNotificationApprise(t *testing.T) {
}
func TestSendTestNotificationAppriseHTTP(t *testing.T) {
t.Setenv("PULSE_DATA_DIR", t.TempDir())
nm := NewNotificationManager("")
defer nm.Stop()
nm.SetEmailConfig(EmailConfig{Enabled: false})
type apprisePayload struct {

View file

@ -38,11 +38,11 @@ type QueuedNotification struct {
Attempts int `json:"attempts"`
MaxAttempts int `json:"maxAttempts"`
LastAttempt *time.Time `json:"lastAttempt,omitempty"`
LastError string `json:"lastError,omitempty"`
LastError *string `json:"lastError,omitempty"`
CreatedAt time.Time `json:"createdAt"`
NextRetryAt *time.Time `json:"nextRetryAt,omitempty"`
CompletedAt *time.Time `json:"completedAt,omitempty"`
PayloadBytes int `json:"payloadBytes,omitempty"`
PayloadBytes *int `json:"payloadBytes,omitempty"`
}
// NotificationQueue manages persistent notification delivery with retries and DLQ