package store import ( "errors" "testing" "time" ) func TestAddHeartbeat_FirstSetsFirstLastAndCount(t *testing.T) { s := New(nil) id := DeviceID("device-1") t0 := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) if err := s.AddHeartbeat(id, t0); err != nil { t.Fatalf("AddHeartbeat: %v", err) } if got := s.Uptime(id); got != 100 { t.Fatalf("uptime = %v, want 100 (1 beat / 1 minute)", got) } } func TestAddHeartbeat_LaterUpdatesLast(t *testing.T) { s := New(nil) id := DeviceID("device-1") t0 := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) t1 := t0.Add(time.Minute) _ = s.AddHeartbeat(id, t0) if err := s.AddHeartbeat(id, t1); err != nil { t.Fatalf("AddHeartbeat: %v", err) } // (2 beats / 1 minute) * 100 if got := s.Uptime(id); got != 200 { t.Fatalf("uptime = %v, want 200", got) } } func TestAddHeartbeat_SameTimestampAccepted(t *testing.T) { s := New(nil) id := DeviceID("device-1") t0 := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) _ = s.AddHeartbeat(id, t0) if err := s.AddHeartbeat(id, t0); err != nil { t.Fatalf("same timestamp should be accepted: %v", err) } // span still zero → 1 minute floor; 2 beats → 200 if got := s.Uptime(id); got != 200 { t.Fatalf("uptime = %v, want 200", got) } } func TestAddHeartbeat_OutOfSequence(t *testing.T) { s := New(nil) id := DeviceID("device-1") t0 := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) t1 := t0.Add(time.Minute) _ = s.AddHeartbeat(id, t1) err := s.AddHeartbeat(id, t0) if !errors.Is(err, ErrOutOfSequence) { t.Fatalf("err = %v, want ErrOutOfSequence", err) } // rejected beat must not affect uptime: still 1 beat → 100 if got := s.Uptime(id); got != 100 { t.Fatalf("uptime = %v, want 100", got) } } func TestUptime_TwoHeartbeatsOverTwoMinutes(t *testing.T) { s := New(nil) id := DeviceID("device-1") t0 := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) _ = s.AddHeartbeat(id, t0) _ = s.AddHeartbeat(id, t0.Add(2*time.Minute)) if got := s.Uptime(id); got != 100 { t.Fatalf("uptime = %v, want 100", got) } } func TestUptime_UnknownDevice(t *testing.T) { s := New(nil) if got := s.Uptime(DeviceID("missing")); got != 0 { t.Fatalf("uptime = %v, want 0", got) } } func TestUptime_IsPerDevice(t *testing.T) { s := New(nil) a := DeviceID("device-1") b := DeviceID("device-2") t0 := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) _ = s.AddHeartbeat(a, t0) _ = s.AddHeartbeat(a, t0.Add(time.Minute)) _ = s.AddHeartbeat(b, t0) if got := s.Uptime(a); got != 200 { t.Fatalf("device-1 uptime = %v, want 200", got) } if got := s.Uptime(b); got != 100 { t.Fatalf("device-2 uptime = %v, want 100 (single beat = 1 minute)", got) } }