96 lines
3.1 KiB
Go
96 lines
3.1 KiB
Go
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")
|
||
|
|
}
|
||
|
|
}
|