test: Add recordTaskResult tests

Add 6 tests for polling task result recording:
- Nil monitor safety check
- Success case resets counters
- Failure case increments counters
- Consecutive failures tracking
- Success resets failure state
- Nil internal maps don't panic

Coverage: 53.7% → 97.6%
This commit is contained in:
rcourtman 2025-12-02 02:06:08 +00:00
parent 67d322c456
commit 2d09c28129

View file

@ -1,6 +1,7 @@
package monitoring
import (
"errors"
"testing"
"time"
@ -85,3 +86,175 @@ func TestRescheduleTaskUsesInstanceIntervalWhenSchedulerDisabled(t *testing.T) {
t.Fatalf("expected NextRun about %v from now, got %v", cfg.PVEPollingInterval, remaining)
}
}
func TestRecordTaskResult_NilMonitor(t *testing.T) {
// Should not panic when called on nil monitor
var m *Monitor
m.recordTaskResult(InstanceTypePVE, "test-instance", nil)
// If we get here without panic, the test passes
}
func TestRecordTaskResult_Success(t *testing.T) {
m := &Monitor{
pollStatusMap: make(map[string]*pollStatus),
failureCounts: make(map[string]int),
lastOutcome: make(map[string]taskOutcome),
circuitBreakers: make(map[string]*circuitBreaker),
}
// Record a success
m.recordTaskResult(InstanceTypePVE, "test-instance", nil)
key := schedulerKey(InstanceTypePVE, "test-instance")
// Verify failure count is reset
if m.failureCounts[key] != 0 {
t.Errorf("expected failureCounts[%s] = 0, got %d", key, m.failureCounts[key])
}
// Verify last outcome is success
outcome, ok := m.lastOutcome[key]
if !ok {
t.Fatalf("expected lastOutcome[%s] to exist", key)
}
if !outcome.success {
t.Error("expected outcome.success = true")
}
// Verify poll status
status, ok := m.pollStatusMap[key]
if !ok {
t.Fatalf("expected pollStatusMap[%s] to exist", key)
}
if status.ConsecutiveFailures != 0 {
t.Errorf("expected ConsecutiveFailures = 0, got %d", status.ConsecutiveFailures)
}
}
func TestRecordTaskResult_Failure(t *testing.T) {
m := &Monitor{
pollStatusMap: make(map[string]*pollStatus),
failureCounts: make(map[string]int),
lastOutcome: make(map[string]taskOutcome),
circuitBreakers: make(map[string]*circuitBreaker),
}
testErr := errors.New("connection refused")
// Record a failure
m.recordTaskResult(InstanceTypePVE, "test-instance", testErr)
key := schedulerKey(InstanceTypePVE, "test-instance")
// Verify failure count is incremented
if m.failureCounts[key] != 1 {
t.Errorf("expected failureCounts[%s] = 1, got %d", key, m.failureCounts[key])
}
// Verify last outcome is failure
outcome, ok := m.lastOutcome[key]
if !ok {
t.Fatalf("expected lastOutcome[%s] to exist", key)
}
if outcome.success {
t.Error("expected outcome.success = false")
}
if outcome.err != testErr {
t.Errorf("expected outcome.err = %v, got %v", testErr, outcome.err)
}
// Verify poll status
status, ok := m.pollStatusMap[key]
if !ok {
t.Fatalf("expected pollStatusMap[%s] to exist", key)
}
if status.ConsecutiveFailures != 1 {
t.Errorf("expected ConsecutiveFailures = 1, got %d", status.ConsecutiveFailures)
}
if status.LastErrorMessage != "connection refused" {
t.Errorf("expected LastErrorMessage = 'connection refused', got %q", status.LastErrorMessage)
}
}
func TestRecordTaskResult_ConsecutiveFailures(t *testing.T) {
m := &Monitor{
pollStatusMap: make(map[string]*pollStatus),
failureCounts: make(map[string]int),
lastOutcome: make(map[string]taskOutcome),
circuitBreakers: make(map[string]*circuitBreaker),
}
testErr := errors.New("timeout")
// Record multiple failures
m.recordTaskResult(InstanceTypePBS, "pbs-server", testErr)
m.recordTaskResult(InstanceTypePBS, "pbs-server", testErr)
m.recordTaskResult(InstanceTypePBS, "pbs-server", testErr)
key := schedulerKey(InstanceTypePBS, "pbs-server")
// Verify consecutive failures count
status := m.pollStatusMap[key]
if status.ConsecutiveFailures != 3 {
t.Errorf("expected ConsecutiveFailures = 3, got %d", status.ConsecutiveFailures)
}
// FirstFailureAt should be set on first failure and not change
if status.FirstFailureAt.IsZero() {
t.Error("expected FirstFailureAt to be set")
}
}
func TestRecordTaskResult_SuccessResetsFailures(t *testing.T) {
m := &Monitor{
pollStatusMap: make(map[string]*pollStatus),
failureCounts: make(map[string]int),
lastOutcome: make(map[string]taskOutcome),
circuitBreakers: make(map[string]*circuitBreaker),
}
testErr := errors.New("error")
key := schedulerKey(InstanceTypePMG, "pmg-server")
// Record some failures first
m.recordTaskResult(InstanceTypePMG, "pmg-server", testErr)
m.recordTaskResult(InstanceTypePMG, "pmg-server", testErr)
if m.pollStatusMap[key].ConsecutiveFailures != 2 {
t.Fatalf("expected 2 failures before reset")
}
// Now record a success
m.recordTaskResult(InstanceTypePMG, "pmg-server", nil)
// Verify everything is reset
if m.failureCounts[key] != 0 {
t.Errorf("expected failureCounts to be reset to 0, got %d", m.failureCounts[key])
}
if m.pollStatusMap[key].ConsecutiveFailures != 0 {
t.Errorf("expected ConsecutiveFailures to be reset to 0, got %d", m.pollStatusMap[key].ConsecutiveFailures)
}
if !m.pollStatusMap[key].FirstFailureAt.IsZero() {
t.Error("expected FirstFailureAt to be reset to zero")
}
}
func TestRecordTaskResult_NilMaps(t *testing.T) {
// Monitor with nil internal maps - should not panic
m := &Monitor{
pollStatusMap: make(map[string]*pollStatus),
failureCounts: nil, // nil
lastOutcome: nil, // nil
circuitBreakers: make(map[string]*circuitBreaker),
}
// Should not panic
m.recordTaskResult(InstanceTypePVE, "test", nil)
m.recordTaskResult(InstanceTypePVE, "test", errors.New("error"))
// pollStatusMap should still be updated
key := schedulerKey(InstanceTypePVE, "test")
if _, ok := m.pollStatusMap[key]; !ok {
t.Error("expected pollStatusMap to be updated even with nil failureCounts/lastOutcome")
}
}