health_test.go
Rama main · réplica de lectura del repositorio original.
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
)
func newMonitor(f *fake) *monitor {
m := &monitor{relay: f.relay, sources: []*source{f.source}, to: f.source.to}
m.lastPoll.Store(time.Now().UnixNano())
return m
}
func TestMonitorPingsAndReportsOnlyStateChanges(t *testing.T) {
f := fakes(t)
var pings atomic.Int32
heartbeat := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Errorf("heartbeat method %s", r.Method)
}
pings.Add(1)
}))
t.Cleanup(heartbeat.Close)
m := newMonitor(f)
m.heartbeat, m.client = heartbeat.URL, heartbeat.Client()
ctx := t.Context()
m.check(ctx)
if pings.Load() != 1 || len(f.alerted()) != 0 {
t.Fatalf("healthy: pings %d, alerts %q", pings.Load(), f.alerted())
}
// The bot lost its admin rights in MAX: one message, no heartbeat
f.maxAdmin.Store(false)
m.check(ctx)
m.check(ctx)
if alerts := f.alerted(); pings.Load() != 1 || len(alerts) != 1 || !strings.Contains(alerts[0], "не администратор канала MAX") {
t.Fatalf("failing: pings %d, alerts %q", pings.Load(), alerts)
}
f.maxAdmin.Store(true)
m.check(ctx)
if alerts := f.alerted(); pings.Load() != 2 || len(alerts) != 2 || !strings.Contains(alerts[1], "снова проходит") {
t.Fatalf("recovered: pings %d, alerts %q", pings.Load(), alerts)
}
}
func TestMonitorNoticesRemovedTelegramAdmin(t *testing.T) {
f := fakes(t)
f.tgAdmin.Store(false)
newMonitor(f).check(t.Context())
if alerts := f.alerted(); len(alerts) != 1 || !strings.Contains(alerts[0], "не администратор канала Telegram «Канал»") {
t.Fatalf("alerts %q", alerts)
}
}
func TestPollingMonitorNoticesForeignWebhook(t *testing.T) {
f := fakes(t)
address := "https://other.example.test/webhook/secret"
f.tgWebhook.Store(&address)
m := newMonitor(f)
m.check(t.Context())
alerts := f.alerted()
if !m.failing || len(alerts) != 1 || !strings.Contains(alerts[0], "настроен вебхук") {
t.Fatalf("foreign webhook not reported: failing=%t, alerts=%q", m.failing, alerts)
}
if strings.Contains(alerts[0], "/webhook/secret") {
t.Fatalf("webhook secret leaked: %q", alerts)
}
}
func TestPollingMonitorStopsHeartbeatUntilPollingRecovers(t *testing.T) {
f := fakes(t)
var pings atomic.Int32
heartbeat := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
pings.Add(1)
}))
t.Cleanup(heartbeat.Close)
m := newMonitor(f)
m.heartbeat, m.client = heartbeat.URL, heartbeat.Client()
m.lastPoll.Store(time.Now().Add(-2*healthInterval - time.Second).UnixNano())
m.check(t.Context())
m.check(t.Context())
if alerts := f.alerted(); pings.Load() != 0 || len(alerts) != 1 || !strings.Contains(alerts[0], "приём постов Telegram не отвечает") {
t.Fatalf("stalled poller: pings=%d, alerts=%q", pings.Load(), alerts)
}
m.lastPoll.Store(time.Now().UnixNano())
m.check(t.Context())
if alerts := f.alerted(); pings.Load() != 1 || len(alerts) != 2 || !strings.Contains(alerts[1], "снова проходит") {
t.Fatalf("recovered poller: pings=%d, alerts=%q", pings.Load(), alerts)
}
}
func TestWebhookMonitorDoesNotRequirePolling(t *testing.T) {
f := fakes(t)
address := "https://our.example.test/webhook"
f.tgWebhook.Store(&address)
m := newMonitor(f)
m.tgWebhookURL = address
m.lastPoll.Store(0)
m.check(t.Context())
if m.failing || len(f.alerted()) != 0 {
t.Fatalf("webhook mode needs no polling: failing=%t, alerts=%q", m.failing, f.alerted())
}
}
func TestChecksRunInParallelWithOwnTimeouts(t *testing.T) {
var running, peak atomic.Int32
checks := make([]func(context.Context) error, 20)
for i := range checks {
checks[i] = func(ctx context.Context) error {
n := running.Add(1)
for p := peak.Load(); n > p && !peak.CompareAndSwap(p, n); p = peak.Load() {
}
defer running.Add(-1)
if i%5 == 0 { // hangs: only its own timeout stops it
<-ctx.Done()
return fmt.Errorf("check %d: %w", i, ctx.Err())
}
time.Sleep(10 * time.Millisecond)
return nil
}
}
problems := runChecks(t.Context(), checks, 4, 100*time.Millisecond)
want := []string{"check 0", "check 5", "check 10", "check 15"}
if len(problems) != len(want) {
t.Fatalf("problems %q", problems)
}
for i, p := range problems {
if !strings.HasPrefix(p, want[i]) {
t.Fatalf("problems %q, want in order %q", problems, want)
}
}
if n := peak.Load(); n > 4 || n < 2 {
t.Fatalf("peak parallelism %d", n)
}
}