package api import ( "testing" "time" ) func TestEmbyHealthWaitsForTheThresholdBeforeReportingAnOutage(t *testing.T) { var health embyHealth start := time.Now().UTC() health.begin(60*time.Second, start) // One failed probe is a hiccup — a restart, a slow scan. A red bar across somebody's // film for that is worse than saying nothing. health.record(false, "", start.Add(time.Minute)) if !health.get().reachable { t.Fatal("a single failure raised an outage") } health.record(false, "", start.Add(2*time.Minute)) state := health.get() if state.reachable { t.Fatal("two consecutive failures did not raise an outage") } if !state.since.Equal(start.Add(2 * time.Minute)) { t.Errorf("since = %v, want the probe that crossed the threshold", state.since) } } // The bar clears itself. One good answer is enough — the failure mode of clearing early // is a bar that comes back, which is far better than one that outlives the outage. func TestEmbyHealthRecoversOnTheFirstSuccess(t *testing.T) { var health embyHealth start := time.Now().UTC() health.begin(60*time.Second, start) health.record(false, "", start.Add(time.Minute)) health.record(false, "", start.Add(2*time.Minute)) health.record(true, "", start.Add(3*time.Minute)) state := health.get() if !state.reachable { t.Fatal("a successful probe did not clear the outage") } if !state.since.Equal(start.Add(3 * time.Minute)) { t.Errorf("since = %v, want the recovery time", state.since) } } // A failure that does not reach the threshold must not accumulate across a recovery, or a // server with one flaky probe an hour eventually reports an outage that never happened. func TestEmbyHealthForgetsIsolatedFailures(t *testing.T) { var health embyHealth start := time.Now().UTC() health.begin(60*time.Second, start) for minute := 1; minute <= 10; minute++ { health.record(minute%2 == 0, "", start.Add(time.Duration(minute)*time.Minute)) } if !health.get().reachable { t.Fatal("alternating failures were reported as an outage") } } // With the probe switched off there is nothing to report, and the client must be told to // ignore the field rather than trust a value nothing updates. func TestEmbyHealthPayloadIsReachableWhenUnmonitored(t *testing.T) { payload := embyHealthFor(embyHealthState{}) if payload.Monitored { t.Error("an unstarted probe reported itself as monitored") } if !payload.Reachable { t.Error("an unmonitored server was reported unreachable") } if payload.Since != "" || payload.CheckedAt != "" { t.Error("an unstarted probe reported timestamps it never took") } } func TestEmbyHealthPayloadCarriesTheRetryInterval(t *testing.T) { var health embyHealth start := time.Now().UTC() health.begin(60*time.Second, start) health.record(false, "", start.Add(time.Minute)) health.record(false, "", start.Add(2*time.Minute)) payload := embyHealthFor(health.get()) if payload.RetrySeconds != 60 { t.Errorf("retrySeconds = %d, want 60", payload.RetrySeconds) } if payload.Reachable { t.Error("payload reported a reachable server during an outage") } if payload.CheckedAt == "" { t.Error("payload omitted the last probe time the bar counts down from") } } // The Settings rail prints Emby's version beside the gateway's, so it has to survive the // outage that is exactly when somebody goes looking at that page. A probe that fails // carries no version, and blanking the last known one would replace a fact that is still // true with nothing. func TestEmbyHealthKeepsTheLastKnownVersionThroughAnOutage(t *testing.T) { var health embyHealth start := time.Now().UTC() health.begin(60*time.Second, start) health.record(true, "4.10.0.21", start.Add(time.Minute)) if got := embyHealthFor(health.get()).Version; got != "4.10.0.21" { t.Fatalf("version = %q, want the version the probe reported", got) } health.record(false, "", start.Add(2*time.Minute)) health.record(false, "", start.Add(3*time.Minute)) payload := embyHealthFor(health.get()) if payload.Reachable { t.Fatal("the outage was not raised") } if payload.Version != "4.10.0.21" { t.Errorf("version = %q, want it kept through the outage", payload.Version) } // And an upgrade is picked up on the probe that sees it. health.record(true, "4.11.0.1", start.Add(4*time.Minute)) if got := embyHealthFor(health.get()).Version; got != "4.11.0.1" { t.Errorf("version = %q, want the newly reported one", got) } } // Omitted rather than empty, so a television can tell "no probe has completed yet" from // "Emby answered without one". func TestEmbyHealthOmitsAnUnknownVersion(t *testing.T) { var health embyHealth start := time.Now().UTC() health.begin(60*time.Second, start) if got := embyHealthFor(health.get()).Version; got != "" { t.Errorf("version = %q, want empty before any probe has answered", got) } }