mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-22 10:12:25 +00:00
Also seed the cursor integration test with MeshMapper membership; it still used channel_iatas after channel lists moved to region scoping.
256 lines
8.1 KiB
Go
256 lines
8.1 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/MeshCore-Beacon/beacon-server/internal/api"
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
func TestListChannels_InvalidLimit(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/channels", listChannels(stubReader{}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels?limit=notanint", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListChannels_InvalidCursor(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/channels", listChannels(stubReader{}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels?cursor=notanint", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListChannels_PageCursor(t *testing.T) {
|
|
called := false
|
|
r := ChannelsRouter(stubReader{listChannels: func(_ context.Context, limit int32, hash []byte, iatas []string, _ *bool, legacy int64, cursor *api.ChannelCursor) (api.ChannelPage, error) {
|
|
called = true
|
|
if limit != 2 || legacy != 0 || cursor == nil || cursor.ID != 9 || !cursor.LastSeen.Equal(time.UnixMicro(1700000000000123)) ||
|
|
!reflect.DeepEqual(hash, []byte{0xaa}) || !reflect.DeepEqual(iatas, []string{"YOW"}) {
|
|
t.Error("page cursor or filters did not reach the reader intact")
|
|
}
|
|
return api.ChannelPage{}, nil
|
|
}})
|
|
for _, legacy := range []string{"", "&cursor=0"} {
|
|
called = false
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/?pageCursor=v1:1700000000000123:9&limit=2&hash=AA&iata=yow"+legacy, nil))
|
|
if w.Code != http.StatusOK || !called {
|
|
t.Fatalf("legacy %q: HTTP %d, reader called=%v", legacy, w.Code, called)
|
|
}
|
|
}
|
|
for _, query := range []string{"pageCursor=bad", "cursor=1&pageCursor=v1:1700000000000123:9", "cursor=bad&pageCursor=v1:1700000000000123:9"} {
|
|
called = false
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/?"+query, nil))
|
|
if w.Code != http.StatusBadRequest || called {
|
|
t.Errorf("HTTP %d, invalid request reached reader=%v", w.Code, called)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestListChannels_InvalidHash(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/channels", listChannels(stubReader{}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels?hash=nothex!!", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListChannels_HashNotSingleByte(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/channels", listChannels(stubReader{}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels?hash=aabb", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestGetChannel_InvalidID(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/channels/{channelID}", getChannel(stubReader{}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels/notanint", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListChannelMessages_InvalidChannelID(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/channels/{channelID}/messages", listChannelMessages(stubReader{}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels/notanint/messages", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListChannelMessages_InvalidLimit(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/channels/{channelID}/messages", listChannelMessages(stubReader{}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels/1/messages?limit=notanint", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListChannelMessages_InvalidSince(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/channels/{channelID}/messages", listChannelMessages(stubReader{}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels/1/messages?since=notanint", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListChannelMessages_InvalidCursor(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/channels/{channelID}/messages", listChannelMessages(stubReader{}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels/1/messages?cursor=notanint", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusBadRequest {
|
|
t.Errorf("expected 400, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListChannels_OK(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/channels", listChannels(stubReader{
|
|
listChannels: func(_ context.Context, _ int32, _ []byte, _ []string, _ *bool, _ int64, _ *api.ChannelCursor) (api.ChannelPage, error) {
|
|
return api.ChannelPage{Page: api.Page[api.ChannelSummary]{Items: []api.ChannelSummary{{ID: 1, ChannelHash: "ab"}}}}, nil
|
|
},
|
|
}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected 200, got %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestListChannels_IATAParsing(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
query string
|
|
want []string
|
|
}{
|
|
{"single lowercased", "?iata=yow", []string{"YOW"}},
|
|
{"multi csv", "?iatas=yow,%20yyz", []string{"YOW", "YYZ"}},
|
|
{"none", "", nil},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var got []string
|
|
r := chi.NewRouter()
|
|
r.Get("/channels", listChannels(stubReader{
|
|
listChannels: func(_ context.Context, _ int32, _ []byte, iatas []string, _ *bool, _ int64, _ *api.ChannelCursor) (api.ChannelPage, error) {
|
|
got = iatas
|
|
return api.ChannelPage{}, nil
|
|
},
|
|
}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels"+tc.query, nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Fatalf("expected 200, got %d", w.Code)
|
|
}
|
|
if !reflect.DeepEqual(got, tc.want) {
|
|
t.Errorf("expected iatas %v, got %v", tc.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListChannels_KeyKnownParsing(t *testing.T) {
|
|
yes, no := true, false
|
|
cases := []struct {
|
|
name string
|
|
query string
|
|
code int
|
|
want *bool
|
|
}{
|
|
{"true", "?keyKnown=true", http.StatusOK, &yes},
|
|
{"false", "?keyKnown=false", http.StatusOK, &no},
|
|
{"absent", "", http.StatusOK, nil},
|
|
{"uppercase", "?keyKnown=TRUE", http.StatusBadRequest, nil},
|
|
{"numeric", "?keyKnown=1", http.StatusBadRequest, nil},
|
|
{"empty", "?keyKnown=", http.StatusBadRequest, nil},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var got *bool
|
|
called := false
|
|
r := chi.NewRouter()
|
|
r.Get("/channels", listChannels(stubReader{
|
|
listChannels: func(_ context.Context, _ int32, _ []byte, _ []string, keyKnown *bool, _ int64, _ *api.ChannelCursor) (api.ChannelPage, error) {
|
|
called = true
|
|
got = keyKnown
|
|
return api.ChannelPage{}, nil
|
|
},
|
|
}))
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/channels"+tc.query, nil))
|
|
if w.Code != tc.code {
|
|
t.Fatalf("expected %d, got %d", tc.code, w.Code)
|
|
}
|
|
if tc.code != http.StatusOK {
|
|
if called {
|
|
t.Error("reader called for invalid keyKnown")
|
|
}
|
|
if !strings.Contains(w.Body.String(), "keyKnown must be true or false") {
|
|
t.Errorf("unexpected error body %s", w.Body.String())
|
|
}
|
|
return
|
|
}
|
|
if !reflect.DeepEqual(got, tc.want) {
|
|
t.Errorf("expected keyKnown %v, got %v", tc.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetChannel_OK(t *testing.T) {
|
|
r := chi.NewRouter()
|
|
r.Get("/channels/{channelID}", getChannel(stubReader{
|
|
getChannel: func(_ context.Context, id int32) (*api.Channel, error) {
|
|
return &api.Channel{ChannelSummary: api.ChannelSummary{ID: int(id)}}, nil
|
|
},
|
|
}))
|
|
req := httptest.NewRequest(http.MethodGet, "/channels/1", nil)
|
|
w := httptest.NewRecorder()
|
|
r.ServeHTTP(w, req)
|
|
if w.Code != http.StatusOK {
|
|
t.Errorf("expected 200, got %d", w.Code)
|
|
}
|
|
}
|