|
- package app
-
- import (
- "testing"
-
- cfgpkg "github.com/jan/fm-rds-tx/internal/config"
- "github.com/jan/fm-rds-tx/internal/output"
- "github.com/jan/fm-rds-tx/internal/platform"
- )
-
- func TestEngineRuntimeStateReporting(t *testing.T) {
- e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
-
- if got := e.Stats().State; got != string(RuntimeStateIdle) {
- t.Fatalf("expected initial state idle, got %s", got)
- }
-
- e.setRuntimeState(RuntimeStatePrebuffering)
- if got := e.Stats().State; got != string(RuntimeStatePrebuffering) {
- t.Fatalf("expected prebuffering, got %s", got)
- }
-
- e.setRuntimeState(RuntimeStateRunning)
- if got := e.currentRuntimeState(); got != RuntimeStateRunning {
- t.Fatalf("currentRuntimeState mismatch: %s", got)
- }
- }
-
- func TestEngineRuntimeStateTransitions(t *testing.T) {
- e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
- e.setRuntimeState(RuntimeStatePrebuffering)
-
- queue := output.QueueStats{Depth: 1, FillLevel: 0.75, Health: output.QueueHealthNormal}
- e.evaluateRuntimeState(queue, false)
- if got := e.currentRuntimeState(); got != RuntimeStateRunning {
- t.Fatalf("expected running after full buffer, got %s", got)
- }
-
- queue.Health = output.QueueHealthCritical
- for i := 0; i < queueCriticalStreakThreshold; i++ {
- e.evaluateRuntimeState(queue, false)
- }
- if got := e.currentRuntimeState(); got != RuntimeStateDegraded {
- t.Fatalf("expected degraded on queue critical streak, got %s", got)
- }
-
- queue.Health = output.QueueHealthNormal
- e.evaluateRuntimeState(queue, false)
- if got := e.currentRuntimeState(); got != RuntimeStateRunning {
- t.Fatalf("expected running once queue healthy, got %s", got)
- }
-
- e.evaluateRuntimeState(queue, true)
- if got := e.currentRuntimeState(); got != RuntimeStateDegraded {
- t.Fatalf("expected degraded when late buffers seen, got %s", got)
- }
- }
-
- func TestEngineRuntimeStateMuteOnPersistentQueueCritical(t *testing.T) {
- e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
- e.setRuntimeState(RuntimeStateRunning)
-
- queue := output.QueueStats{Depth: 1, Health: output.QueueHealthCritical}
- for i := 0; i < queueMutedStreakThreshold; i++ {
- e.evaluateRuntimeState(queue, false)
- }
-
- if got := e.currentRuntimeState(); got != RuntimeStateMuted {
- t.Fatalf("expected muted after prolonged queue critical, got %s", got)
- }
-
- muteFault := e.LastFault()
- if muteFault == nil {
- t.Fatal("expected fault recorded for the mute transition")
- }
- if muteFault.Reason != FaultReasonQueueCritical {
- t.Fatalf("expected queue critical reason, got %s", muteFault.Reason)
- }
- if muteFault.Severity != FaultSeverityMuted {
- t.Fatalf("expected muted severity, got %s", muteFault.Severity)
- }
-
- queue.Health = output.QueueHealthNormal
- for i := 0; i < queueMutedRecoveryThreshold-1; i++ {
- e.evaluateRuntimeState(queue, false)
- if got := e.currentRuntimeState(); got != RuntimeStateMuted {
- t.Fatalf("expected still muted while recovery window builds, got %s", got)
- }
- }
-
- e.evaluateRuntimeState(queue, false)
- if got := e.currentRuntimeState(); got != RuntimeStateDegraded {
- t.Fatalf("expected degrade once mute recovery threshold reached, got %s", got)
- }
-
- recoveryFault := e.LastFault()
- if recoveryFault == nil {
- t.Fatal("expected recovery fault entry after leaving mute")
- }
- if recoveryFault.Severity != FaultSeverityDegraded {
- t.Fatalf("expected degraded severity for recovery event, got %s", recoveryFault.Severity)
- }
- if recoveryFault.Reason != FaultReasonQueueCritical {
- t.Fatalf("expected queue critical reason for recovery event, got %s", recoveryFault.Reason)
- }
-
- e.evaluateRuntimeState(queue, false)
- if got := e.currentRuntimeState(); got != RuntimeStateRunning {
- t.Fatalf("expected running after recovery, got %s", got)
- }
- }
-
- func TestEngineFaultsAfterMutedCriticalStreak(t *testing.T) {
- e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
- e.setRuntimeState(RuntimeStateRunning)
-
- queue := output.QueueStats{Depth: 1, Health: output.QueueHealthCritical}
- for i := 0; i < queueMutedStreakThreshold; i++ {
- e.evaluateRuntimeState(queue, false)
- }
- if got := e.currentRuntimeState(); got != RuntimeStateMuted {
- t.Fatalf("expected muted after draining critical streak, got %s", got)
- }
-
- triggered := false
- for i := 0; i < queueFaultedStreakThreshold; i++ {
- e.evaluateRuntimeState(queue, false)
- if e.currentRuntimeState() == RuntimeStateFaulted {
- triggered = true
- break
- }
- }
- if !triggered {
- t.Fatalf("expected faulted after %d extra critical checks", queueFaultedStreakThreshold)
- }
- if got := e.currentRuntimeState(); got != RuntimeStateFaulted {
- t.Fatalf("expected faulted state, got %s", got)
- }
-
- fault := e.LastFault()
- if fault == nil {
- t.Fatal("expected recorded fault")
- }
- if fault.Severity != FaultSeverityFaulted {
- t.Fatalf("expected faulted severity, got %s", fault.Severity)
- }
- if fault.Reason != FaultReasonQueueCritical {
- t.Fatalf("expected queue critical reason, got %s", fault.Reason)
- }
- }
-
- func TestRuntimeTransitionCounters(t *testing.T) {
- e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
-
- if got := e.Stats().DegradedTransitions; got != 0 {
- t.Fatalf("expected zero transitions initially, got %d", got)
- }
- if got := e.Stats().FaultCount; got != 0 {
- t.Fatalf("expected zero faults initially, got %d", got)
- }
-
- e.setRuntimeState(RuntimeStateDegraded)
- if got := e.Stats().DegradedTransitions; got != 1 {
- t.Fatalf("expected one degraded transition, got %d", got)
- }
-
- e.setRuntimeState(RuntimeStateMuted)
- if got := e.Stats().MutedTransitions; got != 1 {
- t.Fatalf("expected one mute transition, got %d", got)
- }
-
- e.setRuntimeState(RuntimeStateFaulted)
- if got := e.Stats().FaultedTransitions; got != 1 {
- t.Fatalf("expected one faulted transition, got %d", got)
- }
-
- e.recordFault(FaultReasonQueueCritical, FaultSeverityWarn, "audit")
- if got := e.Stats().FaultCount; got != 1 {
- t.Fatalf("expected one recorded fault, got %d", got)
- }
- }
-
-
- func TestEngineResetFaultRequiresFaultedState(t *testing.T) {
- e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
- if err := e.ResetFault(); err == nil {
- t.Fatal("expected error when resetting non-faulted state")
- }
- }
-
- func TestEngineResetFaultTransitionsToDegraded(t *testing.T) {
- e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil))
- e.criticalStreak.Store(7)
- e.mutedRecoveryStreak.Store(3)
- e.mutedFaultStreak.Store(1)
- e.setRuntimeState(RuntimeStateFaulted)
- if err := e.ResetFault(); err != nil {
- t.Fatalf("reset fault failed: %v", err)
- }
- if got := e.currentRuntimeState(); got != RuntimeStateDegraded {
- t.Fatalf("expected degraded after reset, got %s", got)
- }
- if e.criticalStreak.Load() != 0 {
- t.Fatalf("expected critical streak reset, got %d", e.criticalStreak.Load())
- }
- if e.mutedRecoveryStreak.Load() != 0 {
- t.Fatalf("expected mute recovery streak reset, got %d", e.mutedRecoveryStreak.Load())
- }
- if e.mutedFaultStreak.Load() != 0 {
- t.Fatalf("expected mute fault streak reset, got %d", e.mutedFaultStreak.Load())
- }
- if err := e.ResetFault(); err == nil {
- t.Fatal("expected error when resetting after recovery")
- }
- }
|