// SPDX-FileCopyrightText: 2026 Echolot contributors // SPDX-License-Identifier: GPL-3.0-or-later package store import ( "testing" "time" ) func adbStore(t *testing.T, retention time.Duration) *Store { t.Helper() s, err := Open(t.TempDir()) if err != nil { t.Fatal(err) } s.SetADBEndpointRetention(retention) return s } // The port rotates, so a device's previous report is wrong rather than historical: keeping both // would leave an operator trying a dead port. func TestNewestReportWinsPerDevice(t *testing.T) { s := adbStore(t, 24*time.Hour) now := time.Now().UTC() for _, e := range []ADBEndpoint{ {Device: "dev-a", Host: "10.13.102.128", Port: 37089, ReportedAt: now.Add(-2 * time.Minute)}, {Device: "dev-b", Host: "10.13.102.55", Port: 5555, ReportedAt: now.Add(-time.Minute)}, {Device: "dev-a", Host: "10.13.102.128", Port: 33667, ReportedAt: now}, } { if err := s.PutADBEndpoint(e); err != nil { t.Fatal(err) } } got := s.ADBEndpoints() if len(got) != 2 { t.Fatalf("got %d rows, want one per device: %+v", len(got), got) } // Newest first, so the most recently reported device leads. if got[0].Device != "dev-a" || got[0].Port != 33667 { t.Fatalf("newest-first/newest-wins violated: %+v", got) } if got[1].Device != "dev-b" || got[1].Port != 5555 { t.Fatalf("the other device's report was disturbed: %+v", got) } } func TestRetentionForgetsOldEndpoints(t *testing.T) { s := adbStore(t, 24*time.Hour) now := time.Now().UTC() for _, e := range []ADBEndpoint{ {Device: "stale", Host: "10.0.0.9", Port: 5555, ReportedAt: now.Add(-25 * time.Hour)}, {Device: "fresh", Host: "10.0.0.10", Port: 5555, ReportedAt: now}, } { if err := s.PutADBEndpoint(e); err != nil { t.Fatal(err) } } got := s.ADBEndpoints() if len(got) != 1 || got[0].Device != "fresh" { t.Fatalf("retention not enforced on write: %+v", got) } // Reads must age the table too: an idle server still has to forget on schedule, and nobody // writes to this table between one debugging session and the next. s.data.ADBEndpoints[0].ReportedAt = now.Add(-25 * time.Hour) if got := s.ADBEndpoints(); len(got) != 0 { t.Fatalf("read path did not expire entries: %+v", got) } // The drop is real, not just filtered out of the answer on the way past. if len(s.data.ADBEndpoints) != 0 { t.Fatalf("expired rows survived the read: %+v", s.data.ADBEndpoints) } } func TestZeroRetentionKeepsUntilReplaced(t *testing.T) { s := adbStore(t, 0) if err := s.PutADBEndpoint(ADBEndpoint{ Device: "dev", Host: "10.0.0.1", Port: 5555, ReportedAt: time.Now().UTC().Add(-1000 * time.Hour), }); err != nil { t.Fatal(err) } if got := s.ADBEndpoints(); len(got) != 1 { t.Fatal("retention 0 must mean 'keep until replaced', not 'keep nothing'") } } // Dev telemetry must not become unbounded state just because a lot of devices are enrolled. func TestEndpointTableIsBounded(t *testing.T) { s := adbStore(t, 24*time.Hour) now := time.Now().UTC() for i := 0; i < maxADBEndpoints+5; i++ { if err := s.PutADBEndpoint(ADBEndpoint{ Device: string(rune('a'+i)) + "-dev", Host: "10.0.0.1", Port: 5555 + i, ReportedAt: now.Add(time.Duration(i) * time.Second), }); err != nil { t.Fatal(err) } } got := s.ADBEndpoints() if len(got) != maxADBEndpoints { t.Fatalf("table holds %d rows, want the cap of %d", len(got), maxADBEndpoints) } // The rows evicted are the oldest, so the newest report is still there. if got[0].Port != 5555+maxADBEndpoints+4 { t.Fatalf("the newest report was evicted: %+v", got[0]) } } // Restarting the server must not resurrect an address the retention window already dropped. func TestExpiredEndpointsDoNotSurviveReopen(t *testing.T) { dir := t.TempDir() s, err := Open(dir) if err != nil { t.Fatal(err) } s.SetADBEndpointRetention(time.Hour) if err := s.PutADBEndpoint(ADBEndpoint{ Device: "dev", Host: "10.0.0.1", Port: 5555, ReportedAt: time.Now().UTC(), }); err != nil { t.Fatal(err) } // Age it on disk the way wall-clock time would. s.data.ADBEndpoints[0].ReportedAt = time.Now().UTC().Add(-2 * time.Hour) if err := s.save(); err != nil { t.Fatal(err) } again, err := Open(dir) if err != nil { t.Fatal(err) } again.SetADBEndpointRetention(time.Hour) if got := again.ADBEndpoints(); len(got) != 0 { t.Fatalf("a stale LAN address came back after a restart: %+v", got) } }