wingbits client and cli helpers for mgw310 and other devices
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.libretechconsulting.com/rmcguire/wingbits/pkg/types/readsb"
|
||||
)
|
||||
|
||||
// fixtures map a request path to a captured station response. The readsb files
|
||||
// and the Wingbits files are reused from their respective type packages' testdata
|
||||
// so there is a single source of truth for sample payloads.
|
||||
var fixtures = map[string]string{
|
||||
"/readsb/aircraft.json": "../types/readsb/testdata/aircraft.json",
|
||||
"/data/aircraft.json": "../types/readsb/testdata/aircraft.json",
|
||||
"/data/receiver.json": "../types/readsb/testdata/receiver.json",
|
||||
"/data/stats.json": "../types/readsb/testdata/stats.json",
|
||||
"/data/outline.json": "../types/readsb/testdata/outline.json",
|
||||
"/network/diagnostics": "../types/wingbits/testdata/diagnostics.json",
|
||||
"/tailscale/status": "../types/wingbits/testdata/status.json",
|
||||
"/metrics": "../types/wingbits/testdata/metrics.txt",
|
||||
}
|
||||
|
||||
// newTestClient stands up an httptest server that serves the captured station
|
||||
// fixtures from both the Wingbits (readsb/) and tar1090 (data/) roots.
|
||||
func newTestClient(t *testing.T) *Client {
|
||||
t.Helper()
|
||||
mux := http.NewServeMux()
|
||||
for path, file := range fixtures {
|
||||
mux.HandleFunc(path, serveFile(file))
|
||||
}
|
||||
srv := httptest.NewServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
host, port := splitHostPort(t, strings.TrimPrefix(srv.URL, "http://"))
|
||||
c, err := New(
|
||||
WithWingbitsEndpoint(Endpoint{Host: host, Port: port, DataPath: wingbitsReadsbPath}),
|
||||
WithTar1090Endpoint(Endpoint{Host: host, Port: port, DataPath: defaultDataPath}),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func serveFile(file string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, _ *http.Request) {
|
||||
b, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write(b)
|
||||
}
|
||||
}
|
||||
|
||||
func splitHostPort(t *testing.T, hp string) (string, int) {
|
||||
t.Helper()
|
||||
h, p, ok := strings.Cut(hp, ":")
|
||||
if !ok {
|
||||
t.Fatalf("bad host:port %q", hp)
|
||||
}
|
||||
port, err := strconv.Atoi(p)
|
||||
if err != nil {
|
||||
t.Fatalf("bad port %q: %v", p, err)
|
||||
}
|
||||
return h, port
|
||||
}
|
||||
|
||||
func TestClientReadsbEndpoints(t *testing.T) {
|
||||
c := newTestClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
all, err := c.Aircraft(ctx)
|
||||
if err != nil || len(all.Aircraft) == 0 {
|
||||
t.Fatalf("aircraft: %v (%d)", err, len(all.Aircraft))
|
||||
}
|
||||
// Filters supplied to the query are applied before returning.
|
||||
pos, err := c.Aircraft(ctx, readsb.WithPosition())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(pos.Aircraft) == 0 || len(pos.Aircraft) > len(all.Aircraft) {
|
||||
t.Errorf("filtered %d of %d", len(pos.Aircraft), len(all.Aircraft))
|
||||
}
|
||||
|
||||
rc, err := c.Receiver(ctx)
|
||||
if err != nil || !rc.HasLocation() {
|
||||
t.Fatalf("receiver: %v", err)
|
||||
}
|
||||
if _, err := c.Stats(ctx); err != nil {
|
||||
t.Fatalf("stats: %v", err)
|
||||
}
|
||||
o, err := c.Outline(ctx)
|
||||
if err != nil || len(o.ActualRange.Last24h.Points) == 0 {
|
||||
t.Fatalf("outline: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutlineRequiresTar1090(t *testing.T) {
|
||||
c, err := New(WithWingbitsEndpoint(Endpoint{Host: "127.0.0.1"}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := c.Outline(context.Background()); err == nil {
|
||||
t.Error("expected error without tar1090 endpoint")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientWingbitsEndpoints(t *testing.T) {
|
||||
c := newTestClient(t)
|
||||
ctx := context.Background()
|
||||
|
||||
d, err := c.Diagnostics(ctx)
|
||||
if err != nil || d.Hostname == "" {
|
||||
t.Fatalf("diagnostics: %v", err)
|
||||
}
|
||||
if !d.AllReachable() {
|
||||
t.Error("expected fixture probes all reachable")
|
||||
}
|
||||
s, err := c.Status(ctx)
|
||||
if err != nil || s.Hostname == "" {
|
||||
t.Fatalf("status: %v", err)
|
||||
}
|
||||
m, err := c.Metrics(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("metrics: %v", err)
|
||||
}
|
||||
wb := m.Wingbits()
|
||||
if wb.Version == "" {
|
||||
t.Error("expected wingbits_version label")
|
||||
}
|
||||
if !wb.NATS.Connected {
|
||||
t.Error("expected NATS connected in fixture")
|
||||
}
|
||||
if wb.Beast.Received == 0 {
|
||||
t.Error("expected beast received counter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamAircraft(t *testing.T) {
|
||||
c := newTestClient(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
ch := c.StreamAircraft(ctx, time.Hour)
|
||||
select {
|
||||
case u := <-ch:
|
||||
if u.Err != nil || u.Value == nil || len(u.Value.Aircraft) == 0 {
|
||||
t.Fatalf("first update bad: %v", u.Err)
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("no first update")
|
||||
}
|
||||
cancel()
|
||||
for range ch { // channel must drain and close after cancellation
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user