mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-16 03:00:29 +00:00
140 lines
4.7 KiB
Go
140 lines
4.7 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
sqlc "github.com/MeshCore-Beacon/beacon-server/db/sqlc"
|
|
"github.com/MeshCore-Beacon/beacon-server/internal/api"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
type summaryQueryCounter struct {
|
|
pgx.Tx
|
|
calls int
|
|
}
|
|
|
|
func (c *summaryQueryCounter) Query(ctx context.Context, query string, args ...any) (pgx.Rows, error) {
|
|
c.calls++
|
|
return c.Tx.Query(ctx, query, args...)
|
|
}
|
|
|
|
func TestPacketSummariesPostgres(t *testing.T) {
|
|
dsn := os.Getenv("BEACON_TEST_POSTGRES_DSN")
|
|
if dsn == "" {
|
|
t.Skip("set BEACON_TEST_POSTGRES_DSN for PostgreSQL regression")
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
|
defer cancel()
|
|
conn, err := pgx.Connect(ctx, dsn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close(context.Background())
|
|
tx, err := conn.Begin(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer tx.Rollback(context.Background())
|
|
for _, table := range []string{"packets", "packet_observations", "observers", "transport_scopes"} {
|
|
if _, err := tx.Exec(ctx, "CREATE TEMP TABLE "+table+" (LIKE public."+table+" INCLUDING ALL) ON COMMIT DROP"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
_, err = tx.Exec(ctx, `
|
|
INSERT INTO packets(packet_hash,payload_type,payload_version,route_type,raw_payload,raw_header,parsed_payload,first_heard_at,last_heard_at)
|
|
SELECT decode(lpad(to_hex(id),2,'0'),'hex'),kind,0,1,'\x00','\x00',parsed::jsonb,
|
|
'2026-01-01'::timestamptz+id*interval '1 second','2026-01-01'::timestamptz+id*interval '1 second'
|
|
FROM (VALUES
|
|
(1,4,'{"type":"ADVERT","appData":{"name":"MD00-Repeater"}}'),
|
|
(2,4,'{"type":"ADVERT","appData":{"name":"Relay 📡"}}'),
|
|
(3,4,'{"type":"ADVERT","appData":{"name":""}}'),
|
|
(4,4,'{"type":"ADVERT","appData":{}}'),
|
|
(5,4,NULL),
|
|
(6,4,'{"type":"ADVERT","appData":{"name":null}}'),
|
|
(7,4,'{"type":"ADVERT","appData":{"name":42}}'),
|
|
(8,2,'{"appData":{"name":"not an advert"}}'),
|
|
(9,4,'{"type":"ADVERT","appData":[]}'),
|
|
(10,3,'{"type":"ACK","checksum":"01020304"}'),
|
|
(11,3,'{"type":"ACK","checksum":"AABBCCDD"}'),
|
|
(12,3,'{"type":"ACK","checksum":"00000000"}'),
|
|
(13,3,'{"type":"ACK"}'),
|
|
(14,3,'{"type":"ACK","checksum":12345678}'),
|
|
(15,3,'{"type":"ACK","checksum":"gg112233"}'),
|
|
(16,3,'{"type":"ACK","checksum":"1234"}'),
|
|
(17,3,'{"type":"ACK","checksum":"123456789"}'),
|
|
(18,3,'{"type":"TRACE","checksum":"01020304"}'),
|
|
(19,9,'{"type":"TRACE","traceTag":"efbeadde","authCode":4277009102}'),
|
|
(20,9,'{"type":"PING","traceTag":"deadBEEF"}'),
|
|
(21,9,'{"type":"OTHER","traceTag":"01020304"}'),
|
|
(22,9,'{"traceTag":"01020304"}'),
|
|
(23,9,'{"type":"TRACE","traceTag":12345678}'),
|
|
(24,9,'{"type":"TRACE","traceTag":"01020304\n"}'),
|
|
(25,9,'{"type":"TRACE","traceTag":"gg112233"}'),
|
|
(26,9,'{"type":"TRACE","traceTag":null}'),
|
|
(27,2,'{"type":"ACK","checksum":"01020304"}'),
|
|
(28,9,'{"type":"TRACE","traceTag":"00000000"}')
|
|
) v(id,kind,parsed);
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
counter := &summaryQueryCounter{Tx: tx}
|
|
store := &Store{q: sqlc.New(counter)}
|
|
check := func(items []api.PacketSummary) {
|
|
t.Helper()
|
|
if len(items) != 28 {
|
|
t.Fatalf("got %d packets, want 28", len(items))
|
|
}
|
|
for _, item := range items {
|
|
want := map[string]string{
|
|
"01": "MD00-Repeater", "02": "Relay 📡",
|
|
"0a": "ACK 01020304", "0b": "ACK aabbccdd", "0c": "ACK 00000000",
|
|
"13": "TRACE efbeadde", "14": "PING deadbeef", "1c": "TRACE 00000000",
|
|
}[item.PacketHash]
|
|
if want == "" {
|
|
if item.Summary != nil {
|
|
t.Fatalf("unexpected summary for %s: %q", item.PacketHash, *item.Summary)
|
|
}
|
|
} else if item.Summary == nil || *item.Summary != want {
|
|
t.Fatalf("summary for %s = %v, want %q", item.PacketHash, item.Summary, want)
|
|
}
|
|
}
|
|
if counter.calls != 1 {
|
|
t.Fatalf("list made %d queries, want 1", counter.calls)
|
|
}
|
|
counter.calls = 0
|
|
}
|
|
page, err := store.ListPackets(ctx, nil, nil, nil, nil, time.Time{}, time.Time{}, 0, 50)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
check(page.Items) // names remain available before any observation arrives
|
|
_, err = tx.Exec(ctx, `
|
|
INSERT INTO observers(id,public_key) VALUES ('00000000-0000-0000-0000-000000000001','\x01');
|
|
INSERT INTO packet_observations(id,packet_hash,observer_id,iata,heard_at,path_length_byte,hash_size,hop_count)
|
|
SELECT id,decode(lpad(to_hex(id),2,'0'),'hex'),'00000000-0000-0000-0000-000000000001','YVR',
|
|
'2026-01-01'::timestamptz+id*interval '1 second',0,1,0 FROM generate_series(1,28) id;
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, iatas := range [][]string{nil, {"YVR"}} {
|
|
page, err := store.ListPackets(ctx, nil, nil, iatas, nil, time.Time{}, time.Time{}, 0, 50)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
check(page.Items)
|
|
rows, err := store.ListPacketsAfterID(ctx, 0, -1, -1, iatas, "", 50)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
check(rows)
|
|
}
|
|
}
|