mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-09-01 16:48:19 +00:00
tests(db): finish integration db/api tests
adds nodes, observers and packets
This commit is contained in:
@@ -0,0 +1,368 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sqlc "github.com/MeshCore-Beacon/beacon-server/db/sqlc"
|
||||
mockdb "github.com/MeshCore-Beacon/beacon-server/db/sqlc/mock"
|
||||
"github.com/MeshCore-Beacon/beacon-server/internal/ingest"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestUpsertNode_WithRadio(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
freq := float32(915.0)
|
||||
sf := int16(7)
|
||||
bw := float32(125.0)
|
||||
|
||||
mock.EXPECT().
|
||||
UpsertNode(gomock.Any(), gomock.Any()).
|
||||
Return(sqlc.Node{ID: nodeID}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
id, err := store.UpsertNode(context.Background(), ingest.UpsertNodeParams{
|
||||
PublicKey: []byte{0x01},
|
||||
NodeType: 1,
|
||||
Name: "test-node",
|
||||
}, ingest.RadioSettings{FreqMHz: freq, SF: sf, BWKHz: bw})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if id != nodeID {
|
||||
t.Errorf("expected ID %s, got %s", nodeID, id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpsertNode_WithoutRadio(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
mock.EXPECT().
|
||||
UpsertNode(gomock.Any(), gomock.Any()).
|
||||
Return(sqlc.Node{ID: nodeID}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
id, err := store.UpsertNode(context.Background(), ingest.UpsertNodeParams{
|
||||
PublicKey: []byte{0x01},
|
||||
NodeType: 1,
|
||||
Name: "test-node",
|
||||
}, ingest.RadioSettings{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if id != nodeID {
|
||||
t.Errorf("expected ID %s, got %s", nodeID, id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetNodeCapability_BothTrue(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
mock.EXPECT().SetNodeMultibytePaths(gomock.Any(), nodeID).Return(nil)
|
||||
mock.EXPECT().SetNodeMultibyteTraces(gomock.Any(), nodeID).Return(nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
err := store.SetNodeCapability(context.Background(), nodeID, true, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetNodeCapability_PathsOnly(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
mock.EXPECT().SetNodeMultibytePaths(gomock.Any(), nodeID).Return(nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
err := store.SetNodeCapability(context.Background(), nodeID, true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetNodeCapability_NeitherSet(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
// no EXPECT — neither sqlc method should be called
|
||||
store := &Store{q: mock}
|
||||
err := store.SetNodeCapability(context.Background(), nodeID, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListNodes_Pagination(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
lastSeen := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
|
||||
rows := make([]sqlc.ListNodesRow, 3)
|
||||
for i := range rows {
|
||||
rows[i] = sqlc.ListNodesRow{
|
||||
ID: nodeID,
|
||||
PublicKey: []byte{0x01},
|
||||
LastSeen: lastSeen,
|
||||
}
|
||||
}
|
||||
|
||||
mock.EXPECT().
|
||||
ListNodes(gomock.Any(), gomock.Any()).
|
||||
Return(rows, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListNodes(context.Background(), 0, []string{"YVR"}, nil, nil, nil, "", "", 0, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(page.Items) != 2 {
|
||||
t.Errorf("expected 2 items, got %d", len(page.Items))
|
||||
}
|
||||
if !page.HasMore {
|
||||
t.Error("expected HasMore true")
|
||||
}
|
||||
if page.NextCursor == nil {
|
||||
t.Error("expected NextCursor to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListNodes_IATAsUnmarshal(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
iatasJSON := []byte(`[{"iata":"YVR","last_seen":1700000000000}]`)
|
||||
|
||||
mock.EXPECT().
|
||||
ListNodes(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.ListNodesRow{
|
||||
{
|
||||
ID: nodeID,
|
||||
PublicKey: []byte{0x01},
|
||||
Iatas: iatasJSON,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListNodes(context.Background(), 0, nil, nil, nil, nil, "", "", 0, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(page.Items[0].IATAs) != 1 {
|
||||
t.Errorf("expected 1 IATA, got %d", len(page.Items[0].IATAs))
|
||||
}
|
||||
if page.Items[0].IATAs[0].IATA != "YVR" {
|
||||
t.Errorf("expected IATA YVR, got %s", page.Items[0].IATAs[0].IATA)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListNodes_RadioStringFormatting(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
freq := float32(915.0)
|
||||
sf := int16(7)
|
||||
bw := float32(125.0)
|
||||
|
||||
mock.EXPECT().
|
||||
ListNodes(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.ListNodesRow{
|
||||
{
|
||||
ID: nodeID,
|
||||
PublicKey: []byte{0x01},
|
||||
RadioFreqMhz: &freq,
|
||||
RadioSf: &sf,
|
||||
RadioBwKhz: &bw,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListNodes(context.Background(), 0, nil, nil, nil, nil, "", "", 0, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if page.Items[0].Radio == nil {
|
||||
t.Fatal("expected Radio to be set")
|
||||
}
|
||||
if *page.Items[0].Radio != "915.0,125,7" {
|
||||
t.Errorf("expected Radio 915.0,125,7, got %s", *page.Items[0].Radio)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNode_LastAdvertAt(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
lastAdvert := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
|
||||
mock.EXPECT().
|
||||
GetNodeByID(gomock.Any(), nodeID).
|
||||
Return(sqlc.GetNodeByIDRow{
|
||||
ID: nodeID,
|
||||
PublicKey: []byte{0x01},
|
||||
FirstSeen: pgtype.Timestamptz{Time: time.Now().Add(-time.Hour), Valid: true},
|
||||
LastSeen: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||
LastAdvertAt: lastAdvert,
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetNodeNeighbors(gomock.Any(), nodeID).
|
||||
Return([]sqlc.GetNodeNeighborsRow{}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
node, err := store.GetNode(context.Background(), nodeID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if node.LastAdvertAt == nil {
|
||||
t.Fatal("expected LastAdvertAt to be set")
|
||||
}
|
||||
if *node.LastAdvertAt != 1700000000000 {
|
||||
t.Errorf("expected LastAdvertAt 1700000000000, got %d", *node.LastAdvertAt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNode_LastAdvertAtNil(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
mock.EXPECT().
|
||||
GetNodeByID(gomock.Any(), nodeID).
|
||||
Return(sqlc.GetNodeByIDRow{
|
||||
ID: nodeID,
|
||||
PublicKey: []byte{0x01},
|
||||
FirstSeen: pgtype.Timestamptz{Time: time.Now().Add(-time.Hour), Valid: true},
|
||||
LastSeen: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetNodeNeighbors(gomock.Any(), nodeID).
|
||||
Return([]sqlc.GetNodeNeighborsRow{}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
node, err := store.GetNode(context.Background(), nodeID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if node.LastAdvertAt != nil {
|
||||
t.Errorf("expected nil LastAdvertAt, got %d", *node.LastAdvertAt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNodesByIDs_Mapping(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
name := "test-node"
|
||||
|
||||
mock.EXPECT().
|
||||
GetNodesByIDs(gomock.Any(), []uuid.UUID{nodeID}).
|
||||
Return([]sqlc.GetNodesByIDsRow{
|
||||
{
|
||||
ID: nodeID,
|
||||
Name: &name,
|
||||
PublicKey: []byte{0xde, 0xad},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
result, err := store.GetNodesByIDs(context.Background(), []uuid.UUID{nodeID})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
node, ok := result[nodeID]
|
||||
if !ok {
|
||||
t.Fatal("expected nodeID in result map")
|
||||
}
|
||||
if node.PublicKey != "dead" {
|
||||
t.Errorf("expected PublicKey dead, got %s", node.PublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNodeNeighbors_Deduplication(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
neighborID := uuid.MustParse("00000000-0000-0000-0000-000000000002")
|
||||
earlier := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
later := pgtype.Timestamptz{Time: time.UnixMilli(1700000001000), Valid: true}
|
||||
|
||||
mock.EXPECT().
|
||||
GetNodeNeighbors(gomock.Any(), nodeID).
|
||||
Return([]sqlc.GetNodeNeighborsRow{
|
||||
{
|
||||
ID: neighborID,
|
||||
PublicKey: []byte{0x01},
|
||||
Iata: "YVR",
|
||||
ObservationCount: 3,
|
||||
FirstSeen: earlier,
|
||||
LastSeen: earlier,
|
||||
},
|
||||
{
|
||||
ID: neighborID,
|
||||
PublicKey: []byte{0x01},
|
||||
Iata: "YYJ",
|
||||
ObservationCount: 2,
|
||||
FirstSeen: earlier,
|
||||
LastSeen: later,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
neighbors, err := store.GetNodeNeighbors(context.Background(), nodeID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(neighbors) != 1 {
|
||||
t.Fatalf("expected 1 deduplicated neighbor, got %d", len(neighbors))
|
||||
}
|
||||
if neighbors[0].ObservationCount != 5 {
|
||||
t.Errorf("expected ObservationCount 5, got %d", neighbors[0].ObservationCount)
|
||||
}
|
||||
if neighbors[0].IATA != "YYJ" {
|
||||
t.Errorf("expected IATA YYJ (most recent), got %s", neighbors[0].IATA)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNodeNeighbors_DBError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
mock.EXPECT().
|
||||
GetNodeNeighbors(gomock.Any(), nodeID).
|
||||
Return(nil, errors.New("db error"))
|
||||
|
||||
store := &Store{q: mock}
|
||||
_, err := store.GetNodeNeighbors(context.Background(), nodeID)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sqlc "github.com/MeshCore-Beacon/beacon-server/db/sqlc"
|
||||
mockdb "github.com/MeshCore-Beacon/beacon-server/db/sqlc/mock"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestUpsertObserver_NilDisplayName(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
pubkey := []byte{0x01, 0x02}
|
||||
|
||||
mock.EXPECT().
|
||||
UpsertObserver(gomock.Any(), pubkey).
|
||||
Return(sqlc.Observer{ID: observerID, DisplayName: nil}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
id, displayName, err := store.UpsertObserver(context.Background(), pubkey)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if id != observerID {
|
||||
t.Errorf("expected ID %s, got %s", observerID, id)
|
||||
}
|
||||
if displayName != "" {
|
||||
t.Errorf("expected empty displayName, got %s", displayName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpsertObserver_WithDisplayName(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
pubkey := []byte{0x01, 0x02}
|
||||
name := "test-observer"
|
||||
|
||||
mock.EXPECT().
|
||||
UpsertObserver(gomock.Any(), pubkey).
|
||||
Return(sqlc.Observer{ID: observerID, DisplayName: &name}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
_, displayName, err := store.UpsertObserver(context.Background(), pubkey)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if displayName != "test-observer" {
|
||||
t.Errorf("expected displayName test-observer, got %s", displayName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListObservers_Pagination(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
lastStatusAt := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
rows := make([]sqlc.ListObserversRow, 3)
|
||||
for i := range rows {
|
||||
rows[i] = sqlc.ListObserversRow{
|
||||
ID: observerID,
|
||||
LastStatusAt: lastStatusAt,
|
||||
}
|
||||
}
|
||||
|
||||
mock.EXPECT().
|
||||
ListObservers(gomock.Any(), gomock.Any()).
|
||||
Return(rows, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListObservers(context.Background(), []string{"YVR"}, "", "", "", "", "", 0, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(page.Items) != 2 {
|
||||
t.Errorf("expected 2 items, got %d", len(page.Items))
|
||||
}
|
||||
if !page.HasMore {
|
||||
t.Error("expected HasMore true")
|
||||
}
|
||||
if page.NextCursor == nil {
|
||||
t.Error("expected NextCursor to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListObservers_RadioStringFormatting(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
freq := float32(915.0)
|
||||
sf := int16(7)
|
||||
bw := float32(125.0)
|
||||
|
||||
mock.EXPECT().
|
||||
ListObservers(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.ListObserversRow{
|
||||
{
|
||||
ID: observerID,
|
||||
RadioFreqMhz: &freq,
|
||||
RadioSf: &sf,
|
||||
RadioBwKhz: &bw,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListObservers(context.Background(), nil, "", "", "", "", "", 0, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(page.Items) != 1 {
|
||||
t.Fatalf("expected 1 item, got %d", len(page.Items))
|
||||
}
|
||||
if page.Items[0].Radio == nil {
|
||||
t.Fatal("expected Radio to be set")
|
||||
}
|
||||
if *page.Items[0].Radio != "915.0,125,7" {
|
||||
t.Errorf("expected Radio 915.0,125,7, got %s", *page.Items[0].Radio)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListObservers_NilRadioFields(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
mock.EXPECT().
|
||||
ListObservers(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.ListObserversRow{
|
||||
{ID: observerID, RadioFreqMhz: nil, RadioSf: nil, RadioBwKhz: nil},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListObservers(context.Background(), nil, "", "", "", "", "", 0, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if page.Items[0].Radio != nil {
|
||||
t.Errorf("expected nil Radio, got %s", *page.Items[0].Radio)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListObservers_DBError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
ListObservers(gomock.Any(), gomock.Any()).
|
||||
Return(nil, errors.New("db error"))
|
||||
|
||||
store := &Store{q: mock}
|
||||
_, err := store.ListObservers(context.Background(), nil, "", "", "", "", "", 0, 10)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObserver_OnlineStatus(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
obsCount := int64(10)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverByID(gomock.Any(), observerID).
|
||||
Return(sqlc.Observer{
|
||||
ID: observerID,
|
||||
PublicKey: []byte{0x01},
|
||||
ObservationCount: &obsCount,
|
||||
FirstSeen: pgtype.Timestamptz{Time: time.Now().Add(-time.Hour), Valid: true},
|
||||
LastSeen: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||
LastStatusAt: pgtype.Timestamptz{Time: time.Now().Add(-time.Minute), Valid: true},
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverBrokers(gomock.Any(), observerID).
|
||||
Return([]sqlc.GetObserverBrokersRow{}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverScopes(gomock.Any(), observerID).
|
||||
Return([]string{"default"}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverLastIATA(gomock.Any(), observerID).
|
||||
Return("YVR", nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
observer, err := store.GetObserver(context.Background(), observerID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if observer.Status != "online" {
|
||||
t.Errorf("expected status online, got %s", observer.Status)
|
||||
}
|
||||
if observer.IATA != "YVR" {
|
||||
t.Errorf("expected IATA YVR, got %s", observer.IATA)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObserver_OfflineStatus(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
obsCount := int64(10)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverByID(gomock.Any(), observerID).
|
||||
Return(sqlc.Observer{
|
||||
ID: observerID,
|
||||
PublicKey: []byte{0x01},
|
||||
ObservationCount: &obsCount,
|
||||
FirstSeen: pgtype.Timestamptz{Time: time.Now().Add(-time.Hour), Valid: true},
|
||||
LastSeen: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||
LastStatusAt: pgtype.Timestamptz{Time: time.Now().Add(-10 * time.Minute), Valid: true},
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverBrokers(gomock.Any(), observerID).
|
||||
Return([]sqlc.GetObserverBrokersRow{}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverScopes(gomock.Any(), observerID).
|
||||
Return([]string{}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverLastIATA(gomock.Any(), observerID).
|
||||
Return("YVR", nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
observer, err := store.GetObserver(context.Background(), observerID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if observer.Status != "offline" {
|
||||
t.Errorf("expected status offline, got %s", observer.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObserver_BrokerLastPacketAtNil(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
obsCount := int64(10)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverByID(gomock.Any(), observerID).
|
||||
Return(sqlc.Observer{
|
||||
ID: observerID,
|
||||
PublicKey: []byte{0x01},
|
||||
ObservationCount: &obsCount,
|
||||
FirstSeen: pgtype.Timestamptz{Time: time.Now().Add(-time.Hour), Valid: true},
|
||||
LastSeen: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverBrokers(gomock.Any(), observerID).
|
||||
Return([]sqlc.GetObserverBrokersRow{
|
||||
{
|
||||
BrokerName: "mqtt://test",
|
||||
LastPacketAt: pgtype.Timestamptz{Valid: false},
|
||||
LastSeen: pgtype.Timestamptz{Time: time.Now(), Valid: true},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverScopes(gomock.Any(), observerID).
|
||||
Return([]string{}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverLastIATA(gomock.Any(), observerID).
|
||||
Return("YVR", nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
observer, err := store.GetObserver(context.Background(), observerID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(observer.Brokers) != 1 {
|
||||
t.Fatalf("expected 1 broker, got %d", len(observer.Brokers))
|
||||
}
|
||||
if observer.Brokers[0].LastPacketAt != 0 {
|
||||
t.Errorf("expected LastPacketAt 0 for nil, got %d", observer.Brokers[0].LastPacketAt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObserver_DBError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverByID(gomock.Any(), observerID).
|
||||
Return(sqlc.Observer{}, errors.New("db error"))
|
||||
|
||||
store := &Store{q: mock}
|
||||
_, err := store.GetObserver(context.Background(), observerID)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObserverTelemetry_Mapping(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
reportedAt := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
batteryMV := int32(3700)
|
||||
noiseFloor := float32(-90.0)
|
||||
uptime := int64(3600)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverTelemetry(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.GetObserverTelemetryRow{
|
||||
{
|
||||
ReportedAt: reportedAt,
|
||||
BatteryVoltageMv: &batteryMV,
|
||||
NoiseFloorDb: &noiseFloor,
|
||||
UptimeSeconds: &uptime,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
result, err := store.GetObserverTelemetry(context.Background(), observerID, time.Time{}, time.Time{}, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result.Points) != 1 {
|
||||
t.Fatalf("expected 1 point, got %d", len(result.Points))
|
||||
}
|
||||
if result.Points[0].T != 1700000000000 {
|
||||
t.Errorf("expected T 1700000000000, got %d", result.Points[0].T)
|
||||
}
|
||||
if *result.Points[0].BatteryMV != 3700 {
|
||||
t.Errorf("expected BatteryMV 3700, got %d", *result.Points[0].BatteryMV)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObserverTelemetry_Empty(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverTelemetry(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.GetObserverTelemetryRow{}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
result, err := store.GetObserverTelemetry(context.Background(), observerID, time.Time{}, time.Time{}, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result.Points) != 0 {
|
||||
t.Errorf("expected 0 points, got %d", len(result.Points))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObserverTelemetryBucketed_Mapping(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
bucket := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverTelemetryBucketed(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.GetObserverTelemetryBucketedRow{
|
||||
{
|
||||
Bucket: bucket,
|
||||
BatteryVoltageMv: 3700,
|
||||
NoiseFloorDb: -90.0,
|
||||
UptimeSeconds: 3600,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
points, err := store.GetObserverTelemetryBucketed(context.Background(), observerID, time.Time{}, time.Time{}, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(points) != 1 {
|
||||
t.Fatalf("expected 1 point, got %d", len(points))
|
||||
}
|
||||
if points[0].T != 1700000000000 {
|
||||
t.Errorf("expected T 1700000000000, got %d", points[0].T)
|
||||
}
|
||||
if *points[0].BatteryMV != 3700 {
|
||||
t.Errorf("expected BatteryMV 3700, got %d", *points[0].BatteryMV)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListObserverAdverts_Pagination(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
heardAt := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
|
||||
rows := make([]sqlc.ListObserverAdvertsRow, 3)
|
||||
for i := range rows {
|
||||
rows[i] = sqlc.ListObserverAdvertsRow{
|
||||
ID: int64(i + 1),
|
||||
HeardAt: heardAt,
|
||||
}
|
||||
}
|
||||
|
||||
mock.EXPECT().
|
||||
ListObserverAdverts(gomock.Any(), gomock.Any()).
|
||||
Return(rows, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListObserverAdverts(context.Background(), observerID, 0, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(page.Items) != 2 {
|
||||
t.Errorf("expected 2 items, got %d", len(page.Items))
|
||||
}
|
||||
if !page.HasMore {
|
||||
t.Error("expected HasMore true")
|
||||
}
|
||||
if page.NextCursor == nil {
|
||||
t.Error("expected NextCursor to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListObserverAdverts_DBError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
mock.EXPECT().
|
||||
ListObserverAdverts(gomock.Any(), gomock.Any()).
|
||||
Return(nil, errors.New("db error"))
|
||||
|
||||
store := &Store{q: mock}
|
||||
_, err := store.ListObserverAdverts(context.Background(), observerID, 0, 10)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObserverRadio_NilFields(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverRadio(gomock.Any(), observerID).
|
||||
Return(sqlc.GetObserverRadioRow{}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
settings, err := store.GetObserverRadio(context.Background(), observerID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if settings.FreqMHz != 0 {
|
||||
t.Errorf("expected FreqMHz 0, got %f", settings.FreqMHz)
|
||||
}
|
||||
if settings.SF != 0 {
|
||||
t.Errorf("expected SF 0, got %d", settings.SF)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObserverRadio_WithFields(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
freq := float32(915.0)
|
||||
sf := int16(7)
|
||||
bw := float32(125.0)
|
||||
cr := int16(5)
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverRadio(gomock.Any(), observerID).
|
||||
Return(sqlc.GetObserverRadioRow{
|
||||
RadioFreqMhz: &freq,
|
||||
RadioSf: &sf,
|
||||
RadioBwKhz: &bw,
|
||||
RadioCr: &cr,
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
settings, err := store.GetObserverRadio(context.Background(), observerID)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if settings.FreqMHz != 915.0 {
|
||||
t.Errorf("expected FreqMHz 915.0, got %f", settings.FreqMHz)
|
||||
}
|
||||
if settings.SF != 7 {
|
||||
t.Errorf("expected SF 7, got %d", settings.SF)
|
||||
}
|
||||
if settings.BWKHz != 125.0 {
|
||||
t.Errorf("expected BWKHz 125.0, got %f", settings.BWKHz)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsObserverByPubkey_Found(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
pubkey := []byte{0x01, 0x02}
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverByPubkey(gomock.Any(), pubkey).
|
||||
Return(sqlc.Observer{}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
if !store.IsObserverByPubkey(context.Background(), pubkey) {
|
||||
t.Error("expected true for found observer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsObserverByPubkey_NotFound(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
pubkey := []byte{0x01, 0x02}
|
||||
|
||||
mock.EXPECT().
|
||||
GetObserverByPubkey(gomock.Any(), pubkey).
|
||||
Return(sqlc.Observer{}, errors.New("not found"))
|
||||
|
||||
store := &Store{q: mock}
|
||||
if store.IsObserverByPubkey(context.Background(), pubkey) {
|
||||
t.Error("expected false for missing observer")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sqlc "github.com/MeshCore-Beacon/beacon-server/db/sqlc"
|
||||
mockdb "github.com/MeshCore-Beacon/beacon-server/db/sqlc/mock"
|
||||
"github.com/MeshCore-Beacon/beacon-server/internal/ingest"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestUpsertPacket_WithTransportCodes(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
// little-endian: region=1, subregion=2
|
||||
transportCodes := []byte{0x01, 0x00, 0x02, 0x00}
|
||||
|
||||
mock.EXPECT().
|
||||
UpsertPacket(gomock.Any(), gomock.Any()).
|
||||
Return(sqlc.UpsertPacketRow{Inserted: true}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
inserted, err := store.UpsertPacket(context.Background(), ingest.UpsertPacketParams{
|
||||
PacketHash: []byte{0xde, 0xad},
|
||||
TransportCodes: transportCodes,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !inserted {
|
||||
t.Error("expected inserted true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpsertPacket_WithoutTransportCodes(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
UpsertPacket(gomock.Any(), gomock.Any()).
|
||||
Return(sqlc.UpsertPacketRow{Inserted: false}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
inserted, err := store.UpsertPacket(context.Background(), ingest.UpsertPacketParams{
|
||||
PacketHash: []byte{0xde, 0xad},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if inserted {
|
||||
t.Error("expected inserted false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPackets_Pagination(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
heardAt := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
rows := make([]sqlc.ListPacketsRow, 3)
|
||||
for i := range rows {
|
||||
rows[i] = sqlc.ListPacketsRow{
|
||||
PacketHash: []byte{0xde, 0xad},
|
||||
FirstHeardAt: heardAt,
|
||||
LastHeardAt: heardAt,
|
||||
}
|
||||
}
|
||||
|
||||
mock.EXPECT().
|
||||
ListPackets(gomock.Any(), gomock.Any()).
|
||||
Return(rows, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListPackets(context.Background(), 0, 0, nil, "", time.Time{}, time.Time{}, 0, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(page.Items) != 2 {
|
||||
t.Errorf("expected 2 items, got %d", len(page.Items))
|
||||
}
|
||||
if !page.HasMore {
|
||||
t.Error("expected HasMore true")
|
||||
}
|
||||
if page.NextCursor == nil {
|
||||
t.Error("expected NextCursor to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPackets_LatestObserverNil(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
heardAt := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
|
||||
mock.EXPECT().
|
||||
ListPackets(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.ListPacketsRow{
|
||||
{
|
||||
PacketHash: []byte{0xde, 0xad},
|
||||
FirstHeardAt: heardAt,
|
||||
LastHeardAt: heardAt,
|
||||
LatestObserverID: uuid.UUID{}, // zero UUID
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListPackets(context.Background(), 0, 0, nil, "", time.Time{}, time.Time{}, 0, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if page.Items[0].LatestObserver != nil {
|
||||
t.Error("expected nil LatestObserver for zero UUID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPackets_LatestObserverSet(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
heardAt := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
observerName := "test-observer"
|
||||
observerIATA := "YVR"
|
||||
|
||||
mock.EXPECT().
|
||||
ListPackets(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.ListPacketsRow{
|
||||
{
|
||||
PacketHash: []byte{0xde, 0xad},
|
||||
FirstHeardAt: heardAt,
|
||||
LastHeardAt: heardAt,
|
||||
LatestObserverID: observerID,
|
||||
LatestObserverName: &observerName,
|
||||
LatestObserverIata: observerIATA,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListPackets(context.Background(), 0, 0, nil, "", time.Time{}, time.Time{}, 0, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if page.Items[0].LatestObserver == nil {
|
||||
t.Fatal("expected LatestObserver to be set")
|
||||
}
|
||||
if page.Items[0].LatestObserver.IATA != "" && page.Items[0].LatestObserver.IATA != "YVR" {
|
||||
t.Errorf("expected IATA YVR, got %v", page.Items[0].LatestObserver.IATA)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertObservation_Success(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
|
||||
mock.EXPECT().
|
||||
InsertObservation(gomock.Any(), gomock.Any()).
|
||||
Return(sqlc.PacketObservation{ID: 1}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
inserted, err := store.InsertObservation(context.Background(), ingest.InsertObservationParams{
|
||||
PacketHash: []byte{0xde, 0xad},
|
||||
ObserverID: observerID,
|
||||
IATA: "YVR",
|
||||
HeardAt: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !inserted {
|
||||
t.Error("expected inserted true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertObservation_Conflict(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
InsertObservation(gomock.Any(), gomock.Any()).
|
||||
Return(sqlc.PacketObservation{}, pgx.ErrNoRows)
|
||||
|
||||
store := &Store{q: mock}
|
||||
inserted, err := store.InsertObservation(context.Background(), ingest.InsertObservationParams{
|
||||
PacketHash: []byte{0xde, 0xad},
|
||||
HeardAt: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil error on conflict, got %v", err)
|
||||
}
|
||||
if inserted {
|
||||
t.Error("expected inserted false on conflict")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPacket_Basic(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
packetHash := []byte{0xde, 0xad, 0xbe, 0xef}
|
||||
heardAt := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
sourceBroker := "mqtt://test"
|
||||
|
||||
mock.EXPECT().
|
||||
GetPacketByHash(gomock.Any(), packetHash).
|
||||
Return(sqlc.GetPacketByHashRow{
|
||||
PacketHash: packetHash,
|
||||
RawHeader: []byte{0x01},
|
||||
RawPayload: []byte{0x02},
|
||||
ParsedPayload: []byte(`{}`),
|
||||
FirstHeardAt: heardAt,
|
||||
LastHeardAt: heardAt,
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
ListObservationsForPacket(gomock.Any(), packetHash).
|
||||
Return([]sqlc.ListObservationsForPacketRow{
|
||||
{
|
||||
ID: 1,
|
||||
HeardAt: heardAt,
|
||||
Iata: "YVR",
|
||||
SourceBroker: &sourceBroker,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
packet, err := store.GetPacket(context.Background(), packetHash)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if packet.PacketHash != "deadbeef" {
|
||||
t.Errorf("expected PacketHash deadbeef, got %s", packet.PacketHash)
|
||||
}
|
||||
if packet.ObservationCount != 1 {
|
||||
t.Errorf("expected ObservationCount 1, got %d", packet.ObservationCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPacket_TransportCodes(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
packetHash := []byte{0xde, 0xad, 0xbe, 0xef}
|
||||
heardAt := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
sourceBroker := "mqtt://test"
|
||||
hasTransport := true
|
||||
regionCode := int32(1)
|
||||
subRegionCode := int32(2)
|
||||
|
||||
mock.EXPECT().
|
||||
GetPacketByHash(gomock.Any(), packetHash).
|
||||
Return(sqlc.GetPacketByHashRow{
|
||||
PacketHash: packetHash,
|
||||
RawHeader: []byte{0x01},
|
||||
RawPayload: []byte{0x02},
|
||||
ParsedPayload: []byte(`{}`),
|
||||
FirstHeardAt: heardAt,
|
||||
LastHeardAt: heardAt,
|
||||
TransportCodesPresent: &hasTransport,
|
||||
RegionCode: ®ionCode,
|
||||
SubRegionCode: &subRegionCode,
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
ListObservationsForPacket(gomock.Any(), packetHash).
|
||||
Return([]sqlc.ListObservationsForPacketRow{
|
||||
{ID: 1, HeardAt: heardAt, Iata: "YVR", SourceBroker: &sourceBroker},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
packet, err := store.GetPacket(context.Background(), packetHash)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if packet.TransportCodes == nil {
|
||||
t.Fatal("expected TransportCodes to be set")
|
||||
}
|
||||
if packet.TransportCodes.RegionCode != 1 {
|
||||
t.Errorf("expected RegionCode 1, got %d", packet.TransportCodes.RegionCode)
|
||||
}
|
||||
if packet.TransportCodes.SubRegionCode != 2 {
|
||||
t.Errorf("expected SubRegionCode 2, got %d", packet.TransportCodes.SubRegionCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPacket_FirstToLastMs(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
packetHash := []byte{0xde, 0xad, 0xbe, 0xef}
|
||||
sourceBroker := "mqtt://test"
|
||||
t1 := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
t2 := pgtype.Timestamptz{Time: time.UnixMilli(1700000001000), Valid: true}
|
||||
|
||||
mock.EXPECT().
|
||||
GetPacketByHash(gomock.Any(), packetHash).
|
||||
Return(sqlc.GetPacketByHashRow{
|
||||
PacketHash: packetHash,
|
||||
RawHeader: []byte{0x01},
|
||||
RawPayload: []byte{0x02},
|
||||
ParsedPayload: []byte(`{}`),
|
||||
FirstHeardAt: t1,
|
||||
LastHeardAt: t2,
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
ListObservationsForPacket(gomock.Any(), packetHash).
|
||||
Return([]sqlc.ListObservationsForPacketRow{
|
||||
{ID: 1, HeardAt: t1, Iata: "YVR", SourceBroker: &sourceBroker},
|
||||
{ID: 2, HeardAt: t2, Iata: "YVR", SourceBroker: &sourceBroker},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
packet, err := store.GetPacket(context.Background(), packetHash)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if packet.FirstToLastMs != 1000 {
|
||||
t.Errorf("expected FirstToLastMs 1000, got %d", packet.FirstToLastMs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListNodeObservations_Pagination(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
heardAt := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
|
||||
rows := make([]sqlc.ListNodeObservationsRow, 3)
|
||||
for i := range rows {
|
||||
rows[i] = sqlc.ListNodeObservationsRow{
|
||||
ID: int64(i + 1),
|
||||
HeardAt: heardAt,
|
||||
}
|
||||
}
|
||||
|
||||
mock.EXPECT().
|
||||
ListNodeObservations(gomock.Any(), gomock.Any()).
|
||||
Return(rows, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListNodeObservations(context.Background(), nodeID, 0, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(page.Items) != 2 {
|
||||
t.Errorf("expected 2 items, got %d", len(page.Items))
|
||||
}
|
||||
if !page.HasMore {
|
||||
t.Error("expected HasMore true")
|
||||
}
|
||||
if page.NextCursor == nil {
|
||||
t.Error("expected NextCursor to be set")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user