package app import ( "context" "errors" "testing" "time" 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 TestFaultSeverityString(t *testing.T) { cases := []struct { severity FaultSeverity want string }{ {FaultSeverityWarn, "warn"}, {FaultSeverityDegraded, "degraded"}, {FaultSeverityMuted, "muted"}, {FaultSeverityFaulted, "faulted"}, {FaultSeverity(99), "unknown"}, } for _, tc := range cases { t.Run(tc.want, func(t *testing.T) { if got := tc.severity.String(); got != tc.want { t.Fatalf("expected %s, got %s", tc.want, got) } if txt, _ := tc.severity.MarshalText(); string(txt) != tc.want { t.Fatalf("MarshalText mismatch: want %s, got %s", tc.want, txt) } }) } } func TestEngineRecordsQueueCriticalFault(t *testing.T) { e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil)) e.setRuntimeState(RuntimeStateRunning) queue := output.QueueStats{Depth: 3, Health: output.QueueHealthCritical} for i := 0; i < queueCriticalStreakThreshold; i++ { e.evaluateRuntimeState(queue, false) } last := e.LastFault() if last == nil { t.Fatal("expected fault recorded, got nil") } if last.Reason != FaultReasonQueueCritical { t.Fatalf("expected queue critical reason, got %s", last.Reason) } if last.Severity != FaultSeverityDegraded { t.Fatalf("expected degraded severity, got %s", last.Severity) } } func TestEngineRecordsLateBufferFault(t *testing.T) { e := NewEngine(cfgpkg.Default(), platform.NewSimulatedDriver(nil)) e.setRuntimeState(RuntimeStateRunning) queue := output.QueueStats{Depth: 5, Health: output.QueueHealthNormal} e.evaluateRuntimeState(queue, true) last := e.LastFault() if last == nil { t.Fatal("expected fault recorded for late buffers") } if last.Reason != FaultReasonLateBuffers { t.Fatalf("expected late buffer reason, got %s", last.Reason) } if last.Severity != FaultSeverityWarn { t.Fatalf("expected warn severity, got %s", last.Severity) } } func TestEngineRecordsWriteTimeoutFault(t *testing.T) { cfg := cfgpkg.Default() driver := platform.NewSimulatedDriver(&writeErrorBackend{}) eng := NewEngine(cfg, driver) eng.SetChunkDuration(10 * time.Millisecond) ctx := context.Background() if err := eng.Start(ctx); err != nil { t.Fatalf("start: %v", err) } time.Sleep(120 * time.Millisecond) if err := eng.Stop(ctx); err != nil { t.Fatalf("stop: %v", err) } last := eng.LastFault() if last == nil { t.Fatal("expected write timeout fault") } if last.Reason != FaultReasonWriteTimeout { t.Fatalf("expected writeTimeout reason, got %s", last.Reason) } if last.Severity != FaultSeverityWarn { t.Fatalf("expected warn severity, got %s", last.Severity) } } type writeErrorBackend struct{} func (writeErrorBackend) Configure(context.Context, output.BackendConfig) error { return nil } func (writeErrorBackend) Write(context.Context, *output.CompositeFrame) (int, error) { return 0, errors.New("write timeout") } func (writeErrorBackend) Flush(context.Context) error { return nil } func (writeErrorBackend) Close(context.Context) error { return nil } func (writeErrorBackend) Info() output.BackendInfo { return output.BackendInfo{ Name: "write-error", Description: "backend that rejects writes", Capabilities: output.BackendCapabilities{ SupportsComposite: true, }, } }