mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-02 10:03:43 +00:00
90 lines
3.0 KiB
Go
90 lines
3.0 KiB
Go
//go:build browser
|
|
|
|
package e2e
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
cdplog "github.com/chromedp/cdproto/log"
|
|
"github.com/chromedp/cdproto/network"
|
|
"github.com/chromedp/chromedp"
|
|
meshcore "github.com/meshcore-go/meshcore-go"
|
|
|
|
"github.com/MeshTender/MeshTender/internal/store"
|
|
)
|
|
|
|
// TestE2EOrgPublicMap renders the public org Repeaters map in a real browser and
|
|
// verifies the full chain under the strict CSP: the page fetches its points from
|
|
// the cached JSON endpoint (connect-src 'self'), the CARTO vector style loads
|
|
// (connect-src the CARTO host, plus the same-origin MapLibre worker), and the
|
|
// source's own clustering collapses the co-located repeaters. A cluster proves the
|
|
// fetch succeeded and the map is live; assertClean proves no CSP violation.
|
|
func TestE2EOrgPublicMap(t *testing.T) {
|
|
// Served anonymously by the root (marketing) surface.
|
|
srv := newE2EServer(t)
|
|
|
|
owner, err := srv.store.CreateUser(srv.ctx, "mapowner", "")
|
|
if err != nil {
|
|
t.Fatalf("create user: %v", err)
|
|
}
|
|
org, err := srv.store.CreateOrg(srv.ctx, "Map Club", owner.ID)
|
|
if err != nil {
|
|
t.Fatalf("create org: %v", err)
|
|
}
|
|
|
|
// Four repeaters at the *same* coordinates (they can never separate by zoom, so
|
|
// they always cluster) plus two elsewhere — guarantees at least one cluster.
|
|
locs := []struct{ lat, lon float64 }{
|
|
{40.0, -75.0}, {40.0, -75.0}, {40.0, -75.0}, {40.0, -75.0},
|
|
{41.0, -76.0}, {39.0, -74.0},
|
|
}
|
|
for i, loc := range locs {
|
|
id, _ := meshcore.GenerateLocalIdentity(rand.Reader)
|
|
rep, err := srv.store.CreateRepeater(srv.ctx, &store.Repeater{
|
|
OwnerID: owner.ID, Name: fmt.Sprintf("Node %d", i), PublicKeyHex: id.String(),
|
|
RadioFreqHz: 869525000, RadioBwHz: 250000, RadioSF: 11, RadioCR: 5,
|
|
ShowOnPublicOrg: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create repeater %d: %v", i, err)
|
|
}
|
|
if err := srv.store.SetRepeaterLocation(srv.ctx, rep.ID, loc.lat, loc.lon); err != nil {
|
|
t.Fatalf("set location %d: %v", i, err)
|
|
}
|
|
}
|
|
|
|
bctx, cancel, watch := startBrowser(t)
|
|
defer cancel()
|
|
|
|
var counts sourceCounts
|
|
mapURL := srv.rootURL + "/orgs/" + org.Slug + "/repeaters"
|
|
if err := chromedp.Run(bctx,
|
|
network.Enable(),
|
|
cdplog.Enable(),
|
|
chromedp.Navigate(mapURL),
|
|
// The layer only exists once the points fetch has resolved and the style has
|
|
// loaded, so this one wait covers both.
|
|
mapReady("map", "clusters"),
|
|
// Clustering happens on the worker, so the counts settle a beat after the
|
|
// layer appears.
|
|
chromedp.Poll(`(function () {
|
|
return window.MESHTENDER_MAPS["map"].map
|
|
.querySourceFeatures("points").length > 0;
|
|
})()`, nil, chromedp.WithPollingTimeout(30*time.Second)),
|
|
mapEval("map", countIn("points"), &counts),
|
|
); err != nil {
|
|
t.Fatalf("browser run against %s: %v", mapURL, err)
|
|
}
|
|
if counts.Clusters < 1 {
|
|
t.Errorf("no marker cluster rendered; the four co-located repeaters should collapse into one")
|
|
}
|
|
if counts.Features+counts.Clusters < 2 {
|
|
t.Errorf("map rendered %d features and %d clusters, want the outliers drawn alongside the cluster",
|
|
counts.Features, counts.Clusters)
|
|
}
|
|
watch.assertClean(t)
|
|
}
|