From 8a15ea903baf3890bb678ba8ea2206dd96fa1f71 Mon Sep 17 00:00:00 2001 From: you Date: Fri, 24 Apr 2026 15:26:59 +0000 Subject: [PATCH] test: add last_packet_at tests for ingestor and server - Ingestor: verify last_packet_at is NULL after UpsertObserver (status path), set after InsertTransmission, and unchanged by subsequent UpsertObserver calls - Server: verify last_packet_at reads back through GetObservers and GetObserverByID --- cmd/ingestor/db_test.go | 53 +++++++++++++++++++++++++++++++++++++++++ cmd/server/db_test.go | 52 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 2 deletions(-) diff --git a/cmd/ingestor/db_test.go b/cmd/ingestor/db_test.go index d51903f9..12c2da6a 100644 --- a/cmd/ingestor/db_test.go +++ b/cmd/ingestor/db_test.go @@ -569,6 +569,59 @@ func TestInsertTransmissionUpdatesObserverLastSeen(t *testing.T) { } } +func TestLastPacketAtUpdatedOnPacketOnly(t *testing.T) { + s, err := OpenStore(tempDBPath(t)) + if err != nil { + t.Fatal(err) + } + defer s.Close() + + // Insert observer via status path — last_packet_at should be NULL + if err := s.UpsertObserver("obs1", "Observer1", "SJC", nil); err != nil { + t.Fatal(err) + } + + var lastPacketAt sql.NullString + s.db.QueryRow("SELECT last_packet_at FROM observers WHERE id = ?", "obs1").Scan(&lastPacketAt) + if lastPacketAt.Valid { + t.Fatalf("expected last_packet_at to be NULL after UpsertObserver, got %s", lastPacketAt.String) + } + + // Insert a packet from this observer — last_packet_at should be set + data := &PacketData{ + RawHex: "0A00D69F", + Timestamp: "2026-04-24T12:00:00Z", + ObserverID: "obs1", + Hash: "lastpackettest123456", + RouteType: 2, + PayloadType: 2, + PathJSON: "[]", + DecodedJSON: `{"type":"TXT_MSG"}`, + } + if _, err := s.InsertTransmission(data); err != nil { + t.Fatal(err) + } + + s.db.QueryRow("SELECT last_packet_at FROM observers WHERE id = ?", "obs1").Scan(&lastPacketAt) + if !lastPacketAt.Valid { + t.Fatal("expected last_packet_at to be non-NULL after InsertTransmission") + } + if lastPacketAt.String != "2026-04-24T12:00:00Z" { + t.Errorf("expected last_packet_at=2026-04-24T12:00:00Z, got %s", lastPacketAt.String) + } + + // UpsertObserver again (status path) — last_packet_at should NOT change + if err := s.UpsertObserver("obs1", "Observer1", "SJC", nil); err != nil { + t.Fatal(err) + } + + var lastPacketAtAfterStatus sql.NullString + s.db.QueryRow("SELECT last_packet_at FROM observers WHERE id = ?", "obs1").Scan(&lastPacketAtAfterStatus) + if !lastPacketAtAfterStatus.Valid || lastPacketAtAfterStatus.String != lastPacketAt.String { + t.Errorf("UpsertObserver should not change last_packet_at; expected %s, got %v", lastPacketAt.String, lastPacketAtAfterStatus) + } +} + func TestEndToEndIngest(t *testing.T) { s, err := OpenStore(tempDBPath(t)) if err != nil { diff --git a/cmd/server/db_test.go b/cmd/server/db_test.go index 5067a029..7252f58a 100644 --- a/cmd/server/db_test.go +++ b/cmd/server/db_test.go @@ -48,7 +48,8 @@ func setupTestDB(t *testing.T) *DB { radio TEXT, battery_mv INTEGER, uptime_secs INTEGER, - noise_floor REAL + noise_floor REAL, + last_packet_at TEXT DEFAULT NULL ); CREATE TABLE transmissions ( @@ -355,6 +356,10 @@ func TestGetObservers(t *testing.T) { if observers[0].ID != "obs1" { t.Errorf("expected obs1 first (most recent), got %s", observers[0].ID) } + // last_packet_at should be nil since seedTestData doesn't set it + if observers[0].LastPacketAt != nil { + t.Errorf("expected nil LastPacketAt for obs1 from seed, got %v", *observers[0].LastPacketAt) + } } func TestGetObserverByID(t *testing.T) { @@ -369,6 +374,48 @@ func TestGetObserverByID(t *testing.T) { if obs.ID != "obs1" { t.Errorf("expected obs1, got %s", obs.ID) } + // Verify last_packet_at is nil by default + if obs.LastPacketAt != nil { + t.Errorf("expected nil LastPacketAt, got %v", *obs.LastPacketAt) + } +} + +func TestGetObserverLastPacketAt(t *testing.T) { + db := setupTestDB(t) + defer db.Close() + seedTestData(t, db) + + // Set last_packet_at for obs1 + ts := "2026-04-24T12:00:00Z" + db.conn.Exec(`UPDATE observers SET last_packet_at = ? WHERE id = ?`, ts, "obs1") + + // Verify via GetObservers + observers, err := db.GetObservers() + if err != nil { + t.Fatal(err) + } + var obs1 *Observer + for i := range observers { + if observers[i].ID == "obs1" { + obs1 = &observers[i] + break + } + } + if obs1 == nil { + t.Fatal("obs1 not found") + } + if obs1.LastPacketAt == nil || *obs1.LastPacketAt != ts { + t.Errorf("expected LastPacketAt=%s via GetObservers, got %v", ts, obs1.LastPacketAt) + } + + // Verify via GetObserverByID + obs, err := db.GetObserverByID("obs1") + if err != nil { + t.Fatal(err) + } + if obs.LastPacketAt == nil || *obs.LastPacketAt != ts { + t.Errorf("expected LastPacketAt=%s via GetObserverByID, got %v", ts, obs.LastPacketAt) + } } func TestGetObserverByIDNotFound(t *testing.T) { @@ -1109,7 +1156,8 @@ func setupTestDBV2(t *testing.T) *DB { iata TEXT, last_seen TEXT, first_seen TEXT, - packet_count INTEGER DEFAULT 0 + packet_count INTEGER DEFAULT 0, + last_packet_at TEXT DEFAULT NULL ); CREATE TABLE transmissions (