package web import ( "io" "log" "path/filepath" "sync/atomic" "testing" "time" "gitea.dooplex.hu/admin/felhom-controller/internal/settings" ) // R-1282: the household's „later" media restart (03:30) was an in-memory timer — a controller restart before then // (the night's self-update, a power cut) forgot it and the media apps never saw the new drive. The time is now stored; // at start it is re-armed, a time that passed while the controller was down fires at once, and it runs ONCE. // RED-PROOF: make ResumeMediaLater a no-op → the passed case never restarts the apps. func r1282Server(t *testing.T) (*Server, *settings.Settings, *int32) { t.Helper() lg := log.New(io.Discard, "", 0) sett, err := settings.Load(filepath.Join(t.TempDir(), "settings.json"), lg) if err != nil { t.Fatal(err) } var runs int32 old := mediaLaterRestart mediaLaterRestart = func(*Server, string) { atomic.AddInt32(&runs, 1) } t.Cleanup(func() { mediaLaterRestart = old }) return &Server{settings: sett, logger: lg}, sett, &runs } func TestR1282_ALaterRestartThatPassedWhileDownRunsOnceAtStart(t *testing.T) { s, sett, runs := r1282Server(t) if err := sett.SetMediaRestartLater(time.Now().Add(-2 * time.Hour)); err != nil { // 03:30 passed while down t.Fatal(err) } s.ResumeMediaLater() deadline := time.Now().Add(2 * time.Second) for atomic.LoadInt32(runs) == 0 && time.Now().Before(deadline) { time.Sleep(5 * time.Millisecond) } if n := atomic.LoadInt32(runs); n != 1 { t.Fatalf("the passed restart ran %d time(s) at start, want 1", n) } for !sett.GetMediaRestartLater().IsZero() && time.Now().Before(deadline) { time.Sleep(5 * time.Millisecond) } if !sett.GetMediaRestartLater().IsZero() { t.Fatal("the stored time must be cleared once it ran — else the next start runs it again") } // A second start finds nothing: it ran once. s.ResumeMediaLater() time.Sleep(50 * time.Millisecond) if n := atomic.LoadInt32(runs); n != 1 { t.Fatalf("a second start ran it again (%d)", n) } } func TestR1282_AFutureLaterRestartIsKeptOverAStart(t *testing.T) { s, sett, runs := r1282Server(t) at := time.Now().Add(1 * time.Hour) if err := sett.SetMediaRestartLater(at); err != nil { t.Fatal(err) } s.ResumeMediaLater() time.Sleep(50 * time.Millisecond) if n := atomic.LoadInt32(runs); n != 0 { t.Fatalf("a restart due in an hour ran now (%d)", n) } s.mediaMu.Lock() got, armed := s.mediaLaterAt, s.mediaLaterTmr != nil s.mediaMu.Unlock() if !armed || !got.Equal(at.UTC().Truncate(time.Second)) && got.Sub(at).Abs() > time.Second { t.Fatalf("the timer must be armed for %v, got %v (armed %v)", at, got, armed) } if s.mediaLaterTmr != nil { s.mediaLaterTmr.Stop() } }