Тикер офлайн-детекции (internal/healthcheck) переехал из device-control-service в новый самостоятельный Go-сервис — как и планировалось с самого начала (см. изначальный README-заглушку). Детекция online остаётся в device-control-service: она происходит как побочный эффект уже имеющейся там MQTT-подписки на devices/+/telemetry и devices/+/ack, заводить для этого отдельный сервис с дублирующей MQTT-подпиской избыточно. health-check-service — свой Go-модуль (без зависимости от proto/, сборка из собственного контекста), читает только узкое read+offline-подмножество Device Shadow keyspace в Redis (devices:known, status, last_seen) — полный Shadow API с записью desired/reported state остаётся только в device-control-service. Метрика device_control_health_transitions_total переименована в device_health_transitions_total и теперь публикуется с этим именем из ДВУХ сервисов (device-control-service — direction=online, health-check-service — direction=offline): Prometheus агрегирует одноимённые метрики с разных таргетов прозрачно, поэтому Grafana- дашборд адаптирован простой сменой имени метрики в запросе, без переделки панели. Проверено вживую через docker compose: реальный online→offline переход (публикация тестовой телеметрии + ожидание таймаута) корректно долетает до Redis и RabbitMQ (device.status_changed), Prometheus видит новый scrape-таргет как up.
94 lines
2.4 KiB
Go
94 lines
2.4 KiB
Go
package shadow
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/alicebob/miniredis/v2"
|
|
"github.com/redis/go-redis/v9"
|
|
)
|
|
|
|
func newTestStore(t *testing.T) *Store {
|
|
t.Helper()
|
|
mr := miniredis.RunT(t)
|
|
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
|
t.Cleanup(func() { rdb.Close() })
|
|
return New(rdb)
|
|
}
|
|
|
|
// markOnline seeds Redis the way device-control-service's Touch() would,
|
|
// without depending on that service's code.
|
|
func markOnline(t *testing.T, s *Store, deviceID string, lastSeen time.Time) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
if err := s.rdb.SAdd(ctx, knownDevicesKey, deviceID).Err(); err != nil {
|
|
t.Fatalf("seed known device: %v", err)
|
|
}
|
|
if err := s.rdb.Set(ctx, statusKey(deviceID), StatusOnline, 0).Err(); err != nil {
|
|
t.Fatalf("seed status: %v", err)
|
|
}
|
|
if err := s.rdb.Set(ctx, lastSeenKey(deviceID), lastSeen.Unix(), 0).Err(); err != nil {
|
|
t.Fatalf("seed last_seen: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestKnownDevices_ListsSeededDevices(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newTestStore(t)
|
|
markOnline(t, s, "d1", time.Now())
|
|
markOnline(t, s, "d2", time.Now())
|
|
|
|
got, err := s.KnownDevices(ctx)
|
|
if err != nil {
|
|
t.Fatalf("known devices: %v", err)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("got %d known devices, want 2 (%v)", len(got), got)
|
|
}
|
|
}
|
|
|
|
func TestMarkOfflineIfStale(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newTestStore(t)
|
|
markOnline(t, s, "d1", time.Now())
|
|
|
|
changed, err := s.MarkOfflineIfStale(ctx, "d1", time.Hour)
|
|
if err != nil {
|
|
t.Fatalf("mark offline (not stale): %v", err)
|
|
}
|
|
if changed {
|
|
t.Fatal("device just touched should not be considered stale")
|
|
}
|
|
|
|
changed, err = s.MarkOfflineIfStale(ctx, "d1", -time.Second) // any age counts as stale
|
|
if err != nil {
|
|
t.Fatalf("mark offline (stale): %v", err)
|
|
}
|
|
if !changed {
|
|
t.Fatal("expected status to flip to offline")
|
|
}
|
|
|
|
// Already offline: a second call should report no further change.
|
|
changed, err = s.MarkOfflineIfStale(ctx, "d1", -time.Second)
|
|
if err != nil {
|
|
t.Fatalf("mark offline (already offline): %v", err)
|
|
}
|
|
if changed {
|
|
t.Fatal("expected no change once already offline")
|
|
}
|
|
}
|
|
|
|
func TestMarkOfflineIfStale_UnknownDeviceIsNoop(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newTestStore(t)
|
|
|
|
changed, err := s.MarkOfflineIfStale(ctx, "ghost", time.Second)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if changed {
|
|
t.Fatal("unknown device should never be reported as changed")
|
|
}
|
|
}
|