mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-09-01 16:48:19 +00:00
maint(tests): start adding integration tests
first db <> api mapping add filter to test coverage action to ignore generated code like sqlc and swagger docs
This commit is contained in:
@@ -29,13 +29,17 @@ jobs:
|
||||
- name: Run tests with coverage
|
||||
run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
|
||||
|
||||
- name: Filter generated files from coverage
|
||||
run: |
|
||||
grep -v -e "db/sqlc" -e "docs" -e "iatadb/gen" coverage.out > coverage.filtered.out
|
||||
|
||||
- name: Display coverage
|
||||
run: go tool cover -func=coverage.out
|
||||
run: go tool cover -func=coverage.filtered.out
|
||||
|
||||
- name: Extract coverage percentage
|
||||
id: coverage
|
||||
run: |
|
||||
COVERAGE=$(go tool cover -func=coverage.out | grep ^total | awk '{print $3}' | tr -d '%')
|
||||
COVERAGE=$(go tool cover -func=coverage.filtered.out | grep ^total | awk '{print $3}' | tr -d '%')
|
||||
echo "pct=$COVERAGE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update coverage badge
|
||||
|
||||
@@ -7,3 +7,4 @@ notes.md
|
||||
roadmap.md
|
||||
|
||||
binaries/
|
||||
cover.out
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
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/jackc/pgx/v5/pgtype"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestListChannels_Empty(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
ListChannels(gomock.Any(), sqlc.ListChannelsParams{
|
||||
Column1: nil,
|
||||
Column2: "",
|
||||
Column3: pgtype.Timestamptz{},
|
||||
Limit: 11,
|
||||
}).
|
||||
Return([]sqlc.Channel{}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListChannels(context.Background(), 10, nil, "", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(page.Items) != 0 {
|
||||
t.Errorf("expected 0 items, got %d", len(page.Items))
|
||||
}
|
||||
if page.HasMore {
|
||||
t.Error("expected HasMore false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListChannels_Pagination(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
isHashtag := false
|
||||
keyKnown := true
|
||||
lastSeen := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
|
||||
// return limit+1 rows to trigger HasMore
|
||||
rows := make([]sqlc.Channel, 3)
|
||||
for i := range rows {
|
||||
rows[i] = sqlc.Channel{
|
||||
ID: int32(i + 1),
|
||||
ChannelHash: []byte{0xab, 0xcd},
|
||||
LastSeen: lastSeen,
|
||||
IsHashtag: &isHashtag,
|
||||
KeyKnown: &keyKnown,
|
||||
}
|
||||
}
|
||||
|
||||
mock.EXPECT().
|
||||
ListChannels(gomock.Any(), sqlc.ListChannelsParams{
|
||||
Column1: nil,
|
||||
Column2: "",
|
||||
Column3: pgtype.Timestamptz{},
|
||||
Limit: 3, // limit+1
|
||||
}).
|
||||
Return(rows, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListChannels(context.Background(), 2, nil, "", 0)
|
||||
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 TestListChannels_DBError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
ListChannels(gomock.Any(), gomock.Any()).
|
||||
Return(nil, errors.New("db error"))
|
||||
|
||||
store := &Store{q: mock}
|
||||
_, err := store.ListChannels(context.Background(), 10, nil, "", 0)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChannel_Basic(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
isHashtag := false
|
||||
keyKnown := true
|
||||
msgCount := int64(5)
|
||||
lastSeen := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
|
||||
mock.EXPECT().
|
||||
GetChannelByID(gomock.Any(), int32(1)).
|
||||
Return(sqlc.Channel{
|
||||
ID: 1,
|
||||
ChannelHash: []byte{0xab, 0xcd},
|
||||
LastSeen: lastSeen,
|
||||
IsHashtag: &isHashtag,
|
||||
KeyKnown: &keyKnown,
|
||||
MessageCount: &msgCount,
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
ch, err := store.GetChannel(context.Background(), 1)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if ch.ID != 1 {
|
||||
t.Errorf("expected ID 1, got %d", ch.ID)
|
||||
}
|
||||
if ch.ChannelHash != "abcd" {
|
||||
t.Errorf("expected ChannelHash abcd, got %s", ch.ChannelHash)
|
||||
}
|
||||
if ch.MessageCount != 5 {
|
||||
t.Errorf("expected MessageCount 5, got %d", ch.MessageCount)
|
||||
}
|
||||
if ch.KeyFingerprint != nil {
|
||||
t.Errorf("expected nil KeyFingerprint for non-hashtag channel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChannel_HashtagWithFingerprint(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
isHashtag := true
|
||||
keyKnown := true
|
||||
fp := []byte{0xde, 0xad, 0xbe, 0xef}
|
||||
lastSeen := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
|
||||
mock.EXPECT().
|
||||
GetChannelByID(gomock.Any(), int32(2)).
|
||||
Return(sqlc.Channel{
|
||||
ID: 2,
|
||||
ChannelHash: []byte{0x01, 0x02},
|
||||
LastSeen: lastSeen,
|
||||
IsHashtag: &isHashtag,
|
||||
KeyKnown: &keyKnown,
|
||||
KeyFingerprint: fp,
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
ch, err := store.GetChannel(context.Background(), 2)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if ch.KeyFingerprint == nil {
|
||||
t.Fatal("expected KeyFingerprint to be set for hashtag channel")
|
||||
}
|
||||
if *ch.KeyFingerprint != "deadbeef" {
|
||||
t.Errorf("expected KeyFingerprint deadbeef, got %s", *ch.KeyFingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChannel_DBError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
GetChannelByID(gomock.Any(), int32(1)).
|
||||
Return(sqlc.Channel{}, errors.New("db error"))
|
||||
|
||||
store := &Store{q: mock}
|
||||
_, err := store.GetChannel(context.Background(), 1)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListChannelMessages_AllChannels(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
sentAt := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
senderName := "Alice"
|
||||
content := "hello"
|
||||
|
||||
mock.EXPECT().
|
||||
ListAllChannelMessages(gomock.Any(), sqlc.ListAllChannelMessagesParams{
|
||||
Column1: pgtype.Timestamptz{},
|
||||
Column2: "YVR",
|
||||
Column3: "",
|
||||
Column4: int64(0),
|
||||
Limit: 3,
|
||||
}).
|
||||
Return([]sqlc.ListAllChannelMessagesRow{
|
||||
{
|
||||
ID: 1,
|
||||
PacketHashHex: "deadbeef",
|
||||
ChannelHash: []byte{0xab},
|
||||
SenderName: &senderName,
|
||||
Content: &content,
|
||||
SentAt: sentAt,
|
||||
ObservationCount: 2,
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
PacketHashHex: "cafebabe",
|
||||
ChannelHash: []byte{0xcd},
|
||||
SenderName: &senderName,
|
||||
Content: &content,
|
||||
SentAt: sentAt,
|
||||
ObservationCount: 1,
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
PacketHashHex: "deadcafe",
|
||||
ChannelHash: []byte{0xef},
|
||||
SenderName: &senderName,
|
||||
Content: &content,
|
||||
SentAt: sentAt,
|
||||
ObservationCount: 1,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListChannelMessages(context.Background(), nil, time.Time{}, 2, []string{"YVR"}, "", 0)
|
||||
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 TestListChannelMessages_ByChannelID(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
channelID := int32(1)
|
||||
sentAt := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
senderName := "Bob"
|
||||
content := "world"
|
||||
|
||||
mock.EXPECT().
|
||||
ListChannelMessages(gomock.Any(), sqlc.ListChannelMessagesParams{
|
||||
ChannelID: channelID,
|
||||
Column2: pgtype.Timestamptz{},
|
||||
Column3: "YVR",
|
||||
Column4: "",
|
||||
Column5: int64(0),
|
||||
Limit: 3,
|
||||
}).
|
||||
Return([]sqlc.ListChannelMessagesRow{
|
||||
{
|
||||
ID: 1,
|
||||
PacketHashHex: "deadbeef",
|
||||
ChannelHash: []byte{0xab},
|
||||
SenderName: &senderName,
|
||||
Content: &content,
|
||||
SentAt: sentAt,
|
||||
ObservationCount: 1,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
page, err := store.ListChannelMessages(context.Background(), &channelID, time.Time{}, 2, []string{"YVR"}, "", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(page.Items) != 1 {
|
||||
t.Errorf("expected 1 item, got %d", len(page.Items))
|
||||
}
|
||||
if page.HasMore {
|
||||
t.Error("expected HasMore false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListChannelMessages_DBError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
ListAllChannelMessages(gomock.Any(), gomock.Any()).
|
||||
Return(nil, errors.New("db error"))
|
||||
|
||||
store := &Store{q: mock}
|
||||
_, err := store.ListChannelMessages(context.Background(), nil, time.Time{}, 10, nil, "", 0)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
sqlc "github.com/MeshCore-Beacon/beacon-server/db/sqlc"
|
||||
mockdb "github.com/MeshCore-Beacon/beacon-server/db/sqlc/mock"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestListIATAs(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
lat := 49.1967
|
||||
lng := -123.1815
|
||||
name := "Vancouver"
|
||||
|
||||
mock.EXPECT().
|
||||
ListIATAs(gomock.Any()).
|
||||
Return([]sqlc.IataCode{
|
||||
{Iata: "YVR", DisplayName: &name, ApproxLat: &lat, ApproxLng: &lng},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
iatas, err := store.ListIATAs(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(iatas) != 1 {
|
||||
t.Fatalf("expected 1 iata, got %d", len(iatas))
|
||||
}
|
||||
if iatas[0].IATA != "YVR" {
|
||||
t.Errorf("expected IATA YVR, got %s", iatas[0].IATA)
|
||||
}
|
||||
if *iatas[0].Lat != lat {
|
||||
t.Errorf("expected Lat %f, got %f", lat, *iatas[0].Lat)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIATA(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
lat := 49.1967
|
||||
lng := -123.1815
|
||||
name := "Vancouver"
|
||||
|
||||
mock.EXPECT().
|
||||
GetIATA(gomock.Any(), "YVR").
|
||||
Return(sqlc.IataCode{Iata: "YVR", DisplayName: &name, ApproxLat: &lat, ApproxLng: &lng}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
iata, err := store.GetIATA(context.Background(), "YVR")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if iata.IATA != "YVR" {
|
||||
t.Errorf("expected YVR, got %s", iata.IATA)
|
||||
}
|
||||
if *iata.Lng != lng {
|
||||
t.Errorf("expected Lng %f, got %f", lng, *iata.Lng)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRegion_WithZoomLevel(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
zoom := int32(10)
|
||||
|
||||
mock.EXPECT().
|
||||
GetRegion(gomock.Any(), int32(1)).
|
||||
Return(sqlc.GetRegionRow{
|
||||
ID: 1,
|
||||
Slug: "bc",
|
||||
Name: "British Columbia",
|
||||
ZoomLevel: &zoom,
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetRegionIATAs(gomock.Any(), int32(1)).
|
||||
Return([]string{"YVR", "YYJ"}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
region, err := store.GetRegion(context.Background(), 1)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if region.Slug != "bc" {
|
||||
t.Errorf("expected slug bc, got %s", region.Slug)
|
||||
}
|
||||
if region.ZoomLevel == nil {
|
||||
t.Fatal("expected ZoomLevel to be set")
|
||||
}
|
||||
if *region.ZoomLevel != 10 {
|
||||
t.Errorf("expected ZoomLevel 10, got %d", *region.ZoomLevel)
|
||||
}
|
||||
if len(region.IATAs) != 2 {
|
||||
t.Errorf("expected 2 IATAs, got %d", len(region.IATAs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRegion_IATAError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
GetRegion(gomock.Any(), int32(1)).
|
||||
Return(sqlc.GetRegionRow{ID: 1, Slug: "bc", Name: "British Columbia"}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetRegionIATAs(gomock.Any(), int32(1)).
|
||||
Return(nil, errors.New("db error"))
|
||||
|
||||
store := &Store{q: mock}
|
||||
_, err := store.GetRegion(context.Background(), 1)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRegionBySlug(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
GetRegionBySlug(gomock.Any(), "bc").
|
||||
Return(sqlc.GetRegionBySlugRow{ID: 1, Slug: "bc", Name: "British Columbia"}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
GetRegionIATAs(gomock.Any(), int32(1)).
|
||||
Return([]string{"YVR"}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
region, err := store.GetRegionBySlug(context.Background(), "bc")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if region.Slug != "bc" {
|
||||
t.Errorf("expected slug bc, got %s", region.Slug)
|
||||
}
|
||||
if len(region.IATAs) != 1 {
|
||||
t.Errorf("expected 1 IATA, got %d", len(region.IATAs))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
sqlc "github.com/MeshCore-Beacon/beacon-server/db/sqlc"
|
||||
mockdb "github.com/MeshCore-Beacon/beacon-server/db/sqlc/mock"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestGetTransportScopes(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
GetTransportScopes(gomock.Any()).
|
||||
Return([]sqlc.GetTransportScopesRow{
|
||||
{
|
||||
Name: "default",
|
||||
TransportKey: []byte{0x01, 0x02},
|
||||
KeyFingerprint: []byte{0xde, 0xad},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
entries, err := store.GetTransportScopes(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(entries) != 1 {
|
||||
t.Fatalf("expected 1 entry, got %d", len(entries))
|
||||
}
|
||||
if entries[0].Name != "default" {
|
||||
t.Errorf("expected Name default, got %s", entries[0].Name)
|
||||
}
|
||||
if !bytes.Equal(entries[0].TransportKey, []byte{0x01, 0x02}) {
|
||||
t.Errorf("expected TransportKey [0x01 0x02], got %v", entries[0].TransportKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetScopesByIATAs(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
GetScopesByIATAs(gomock.Any(), "YVR,YYJ").
|
||||
Return([]sqlc.GetScopesByIATAsRow{
|
||||
{Name: "default", ObserverCount: 3, NodeCount: 10, IataCount: 2},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
items, err := store.GetScopesByIATAs(context.Background(), []string{"YVR", "YYJ"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("expected 1 item, got %d", len(items))
|
||||
}
|
||||
if items[0].IATACount != 2 {
|
||||
t.Errorf("expected IATACount 2, got %d", items[0].IATACount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetScopeByName(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
GetScopeByName(gomock.Any(), "default").
|
||||
Return(sqlc.GetScopeByNameRow{
|
||||
Name: "default",
|
||||
PacketCount: 100,
|
||||
ObserverCount: 5,
|
||||
NodeCount: 20,
|
||||
IataCount: 3,
|
||||
Iatas: []string{"YVR", "YYJ", "YYC"},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
detail, err := store.GetScopeByName(context.Background(), "default")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if detail.Name != "default" {
|
||||
t.Errorf("expected Name default, got %s", detail.Name)
|
||||
}
|
||||
if len(detail.IATAs) != 3 {
|
||||
t.Errorf("expected 3 IATAs, got %d", len(detail.IATAs))
|
||||
}
|
||||
if detail.PacketCount != 100 {
|
||||
t.Errorf("expected PacketCount 100, got %d", detail.PacketCount)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,162 @@
|
||||
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/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestGetStatsOverview(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
GetStatsOverview(gomock.Any(), "YVR").
|
||||
Return(sqlc.GetStatsOverviewRow{
|
||||
TotalPackets: 100,
|
||||
TotalObservations: 500,
|
||||
ActiveObservers: 10,
|
||||
ActiveIatas: 3,
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
result, err := store.GetStatsOverview(context.Background(), []string{"YVR"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if result.TotalPackets != 100 {
|
||||
t.Errorf("expected TotalPackets 100, got %d", result.TotalPackets)
|
||||
}
|
||||
if result.WindowHours != 24 {
|
||||
t.Errorf("expected WindowHours 24, got %d", result.WindowHours)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStatsTopNodes_NilObservationCount(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
lastHeard := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
name := "test-node"
|
||||
|
||||
mock.EXPECT().
|
||||
GetTopNodes(gomock.Any(), sqlc.GetTopNodesParams{
|
||||
Column1: "YVR",
|
||||
Limit: 5,
|
||||
}).
|
||||
Return([]sqlc.MvTopNodesByIatum{
|
||||
{
|
||||
NodeID: nodeID,
|
||||
Name: &name,
|
||||
NodeType: 1,
|
||||
Iata: "YVR",
|
||||
ObservationCount: nil,
|
||||
LastHeard: lastHeard,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
items, err := store.GetStatsTopNodes(context.Background(), []string{"YVR"}, 5)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("expected 1 item, got %d", len(items))
|
||||
}
|
||||
if items[0].ObservationCount != 0 {
|
||||
t.Errorf("expected ObservationCount 0 for nil, got %d", items[0].ObservationCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStatsTopObservers_IATATypeAssertion(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
observerID := uuid.MustParse("00000000-0000-0000-0000-000000000002")
|
||||
displayName := "test-observer"
|
||||
|
||||
obsType := "fixed"
|
||||
mock.EXPECT().
|
||||
GetStatsTopObservers(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.GetStatsTopObserversRow{
|
||||
{
|
||||
ID: observerID,
|
||||
DisplayName: &displayName,
|
||||
ObserverType: &obsType,
|
||||
Iata: "YVR",
|
||||
ObservationCount: 42,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
items, err := store.GetStatsTopObservers(context.Background(), []string{"YVR"}, time.Now().Add(-time.Hour), 5)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("expected 1 item, got %d", len(items))
|
||||
}
|
||||
if items[0].IATA != "YVR" {
|
||||
t.Errorf("expected IATA YVR, got %s", items[0].IATA)
|
||||
}
|
||||
if *items[0].ObserverType != "fixed" {
|
||||
t.Errorf("expected ObserverType fixed, got %s", *items[0].ObserverType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStatsNodeTypes_Mapping(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
GetStatsNodeTypes(gomock.Any(), "YVR").
|
||||
Return([]sqlc.GetStatsNodeTypesRow{
|
||||
{NodeType: 1, Count: 10},
|
||||
{NodeType: 2, Count: 5},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
result, err := store.GetStatsNodeTypes(context.Background(), []string{"YVR"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("expected 2 items, got %d", len(result))
|
||||
}
|
||||
if result[0].NodeTypeName == "" {
|
||||
t.Error("expected NodeTypeName to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetScopeStats(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
GetScopeStats(gomock.Any()).
|
||||
Return([]sqlc.GetScopeStatsRow{
|
||||
{Name: "default", PacketCount: 100, ObserverCount: 5, NodeCount: 20},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
items, err := store.GetScopeStats(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("expected 1 item, got %d", len(items))
|
||||
}
|
||||
if items[0].Name != "default" {
|
||||
t.Errorf("expected Name default, got %s", items[0].Name)
|
||||
}
|
||||
if items[0].PacketCount != 100 {
|
||||
t.Errorf("expected PacketCount 100, got %d", items[0].PacketCount)
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,16 @@
|
||||
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 TestNullableUUID_Zero(t *testing.T) {
|
||||
@@ -89,3 +94,88 @@ func TestToChannelMessage_NilFields(t *testing.T) {
|
||||
t.Errorf("expected empty Content, got %s", msg.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePathHashes_Empty(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
// no EXPECT — sqlc should never be called
|
||||
store := &Store{q: mock}
|
||||
|
||||
result, err := store.ResolvePathHashes(context.Background(), "YVR", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if result != nil {
|
||||
t.Errorf("expected nil, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePathHashes_DBError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
hashes := [][]byte{{0x01, 0x02}}
|
||||
mock.EXPECT().
|
||||
ResolvePathHashes(gomock.Any(), sqlc.ResolvePathHashesParams{
|
||||
Iata: "YVR",
|
||||
Column2: hashes,
|
||||
}).
|
||||
Return(nil, errors.New("db error"))
|
||||
|
||||
store := &Store{q: mock}
|
||||
result, err := store.ResolvePathHashes(context.Background(), "YVR", hashes)
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if result != nil {
|
||||
t.Errorf("expected nil result on error, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePathHashes_Mapping(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
nodeID := uuid.MustParse("00000000-0000-0000-0000-000000000001")
|
||||
name := "test-node"
|
||||
lat := 49.1967
|
||||
lon := -123.1815
|
||||
pubkey := []byte{0xde, 0xad}
|
||||
hashes := [][]byte{{0xab, 0xcd}}
|
||||
|
||||
mock.EXPECT().
|
||||
ResolvePathHashes(gomock.Any(), sqlc.ResolvePathHashesParams{
|
||||
Iata: "YVR",
|
||||
Column2: hashes,
|
||||
}).
|
||||
Return([]sqlc.ResolvePathHashesRow{
|
||||
{
|
||||
Hash: []byte{0xab, 0xcd},
|
||||
NodeID: nodeID,
|
||||
Name: &name,
|
||||
Latitude: &lat,
|
||||
Longitude: &lon,
|
||||
PublicKey: pubkey,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
result, err := store.ResolvePathHashes(context.Background(), "YVR", hashes)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
entries, ok := result["abcd"]
|
||||
if !ok {
|
||||
t.Fatal("expected key abcd in result")
|
||||
}
|
||||
if len(entries) != 1 {
|
||||
t.Fatalf("expected 1 entry, got %d", len(entries))
|
||||
}
|
||||
if entries[0].NodeID != nodeID {
|
||||
t.Errorf("expected NodeID %s, got %s", nodeID, entries[0].NodeID)
|
||||
}
|
||||
if entries[0].Name != &name {
|
||||
t.Errorf("expected Name %s, got %v", name, entries[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
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/jackc/pgx/v5/pgtype"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestListTraceTags_Empty(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
ListTraceTags(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.ListTraceTagsRow{}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
items, err := store.ListTraceTags(context.Background(), []string{"YVR"}, "", "", time.Time{}, time.Time{}, time.Time{}, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(items) != 0 {
|
||||
t.Errorf("expected 0 items, got %d", len(items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTraceTags_WithPayload(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
firstHeard := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
lastHeard := pgtype.Timestamptz{Time: time.UnixMilli(1700000001000), Valid: true}
|
||||
payload := []byte(`{"pathHashes":["aabb","ccdd"],"snrValues":[10,20]}`)
|
||||
|
||||
mock.EXPECT().
|
||||
ListTraceTags(gomock.Any(), gomock.Any()).
|
||||
Return([]sqlc.ListTraceTagsRow{
|
||||
{
|
||||
TraceTag: "trace-001",
|
||||
FirstHeardAt: firstHeard,
|
||||
LastHeardAt: lastHeard,
|
||||
PacketCount: 3,
|
||||
IataCount: 1,
|
||||
TraceType: "trace",
|
||||
BestPayload: payload,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
items, err := store.ListTraceTags(context.Background(), []string{"YVR"}, "", "", time.Time{}, time.Time{}, time.Time{}, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("expected 1 item, got %d", len(items))
|
||||
}
|
||||
if items[0].TraceTag != "trace-001" {
|
||||
t.Errorf("expected TraceTag trace-001, got %s", items[0].TraceTag)
|
||||
}
|
||||
if len(items[0].PathHashes) != 2 {
|
||||
t.Errorf("expected 2 path hashes, got %d", len(items[0].PathHashes))
|
||||
}
|
||||
if items[0].SNRValues[0] != 10 {
|
||||
t.Errorf("expected SNR 10, got %f", items[0].SNRValues[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTraceByTag_Empty(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
GetPacketsByTraceTag(gomock.Any(), "trace-001").
|
||||
Return([]sqlc.GetPacketsByTraceTagRow{}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
detail, err := store.GetTraceByTag(context.Background(), "trace-001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if detail != nil {
|
||||
t.Errorf("expected nil for empty result, got %v", detail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTraceByTag_WithPacket(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
firstHeard := pgtype.Timestamptz{Time: time.UnixMilli(1700000000000), Valid: true}
|
||||
lastHeard := pgtype.Timestamptz{Time: time.UnixMilli(1700000001000), Valid: true}
|
||||
// aabbccdd is valid hex for the packet hash
|
||||
parsedPayload := []byte(`{"pathHashes":["aabb"],"snrValues":[15.0],"flags":0}`)
|
||||
|
||||
scopeName := "default"
|
||||
mock.EXPECT().
|
||||
GetPacketsByTraceTag(gomock.Any(), "trace-001").
|
||||
Return([]sqlc.GetPacketsByTraceTagRow{
|
||||
{
|
||||
PacketHashHex: "aabbccdd",
|
||||
RouteType: 1,
|
||||
ScopeName: &scopeName,
|
||||
FirstHeardAt: firstHeard,
|
||||
LastHeardAt: lastHeard,
|
||||
ParsedPayload: parsedPayload,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
ListObservationsForPacket(gomock.Any(), []byte{0xaa, 0xbb, 0xcc, 0xdd}).
|
||||
Return([]sqlc.ListObservationsForPacketRow{
|
||||
{Iata: "YVR"},
|
||||
}, nil)
|
||||
|
||||
mock.EXPECT().
|
||||
ResolvePathHashes(gomock.Any(), sqlc.ResolvePathHashesParams{
|
||||
Iata: "YVR",
|
||||
Column2: [][]byte{{0xaa, 0xbb}},
|
||||
}).
|
||||
Return([]sqlc.ResolvePathHashesRow{}, nil)
|
||||
|
||||
store := &Store{q: mock}
|
||||
detail, err := store.GetTraceByTag(context.Background(), "trace-001")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if detail == nil {
|
||||
t.Fatal("expected detail, got nil")
|
||||
}
|
||||
if detail.TraceTag != "trace-001" {
|
||||
t.Errorf("expected TraceTag trace-001, got %s", detail.TraceTag)
|
||||
}
|
||||
if len(detail.Packets) != 1 {
|
||||
t.Fatalf("expected 1 packet, got %d", len(detail.Packets))
|
||||
}
|
||||
if len(detail.Packets[0].RawPath) != 1 {
|
||||
t.Errorf("expected 1 raw hop, got %d", len(detail.Packets[0].RawPath))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTraceByTag_DBError(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mockdb.NewMockQuerier(ctrl)
|
||||
|
||||
mock.EXPECT().
|
||||
GetPacketsByTraceTag(gomock.Any(), "trace-001").
|
||||
Return(nil, errors.New("db error"))
|
||||
|
||||
store := &Store{q: mock}
|
||||
_, err := store.GetTraceByTag(context.Background(), "trace-001")
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ require (
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
|
||||
@@ -78,6 +78,8 @@ github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
|
||||
Reference in New Issue
Block a user