pkg/telemetry/telemetry_test.go
Rama master · réplica de lectura del repositorio original.
package telemetry_test
import (
"compress/gzip"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"sync"
"testing"
"github.com/shellhub-io/shellhub/pkg/telemetry"
log "github.com/sirupsen/logrus"
logtest "github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type wireEvent struct {
Event string `json:"event"`
DistinctID string `json:"distinct_id"`
UUID string `json:"uuid"`
Options map[string]any `json:"options"`
Properties map[string]any `json:"properties"`
}
type request struct {
path string
authorization string
encoding string
createdAt string
batch []wireEvent
}
type collector struct {
mu sync.Mutex
requests []request
drop string
listener *httptest.Server
}
func newCollector(t *testing.T, drop string) *collector {
t.Helper()
c := &collector{drop: drop}
c.listener = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body io.Reader = r.Body
if r.Header.Get("Content-Encoding") == "gzip" {
reader, err := gzip.NewReader(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
body = reader
}
var envelope struct {
CreatedAt string `json:"created_at"`
Batch []wireEvent `json:"batch"`
}
if err := json.NewDecoder(body).Decode(&envelope); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
results := map[string]map[string]string{}
for _, event := range envelope.Batch {
result := "ok"
if event.Event == c.drop {
result = "drop"
}
results[event.UUID] = map[string]string{"result": result}
}
c.mu.Lock()
c.requests = append(c.requests, request{
path: r.URL.Path,
authorization: r.Header.Get("Authorization"),
encoding: r.Header.Get("Content-Encoding"),
createdAt: envelope.CreatedAt,
batch: envelope.Batch,
})
c.mu.Unlock()
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"results": results})
}))
t.Cleanup(c.listener.Close)
return c
}
func (c *collector) events() []wireEvent {
c.mu.Lock()
defer c.mu.Unlock()
var all []wireEvent
for _, request := range c.requests {
all = append(all, request.batch...)
}
return all
}
func heartbeat() telemetry.Event {
return telemetry.Event{
Name: "instance heartbeat",
DistinctID: "instance-1",
Properties: map[string]any{"edition": "community", "devices_accepted": 10},
}
}
func TestClientSendsCaptureV1(t *testing.T) {
c := newCollector(t, "")
client, err := telemetry.New(c.listener.URL, "community-write-key")
require.NoError(t, err)
require.NoError(t, client.Send(
heartbeat(),
telemetry.Event{
Name: "namespace heartbeat",
DistinctID: "instance-1",
Groups: map[string]string{"namespace": "hashed-tenant"},
},
))
require.NoError(t, client.Close())
c.mu.Lock()
require.Len(t, c.requests, 1)
sent := c.requests[0]
c.mu.Unlock()
assert.Equal(t, "/i/v1/analytics/events", sent.path)
assert.Equal(t, "Bearer community-write-key", sent.authorization)
assert.Equal(t, "gzip", sent.encoding)
assert.NotEmpty(t, sent.createdAt)
require.Len(t, sent.batch, 2)
event := sent.batch[0]
assert.Equal(t, "instance heartbeat", event.Event)
assert.Equal(t, "instance-1", event.DistinctID)
assert.NotEmpty(t, event.UUID)
assert.Equal(t, "community", event.Properties["edition"])
assert.InDelta(t, telemetry.SchemaVersion, event.Properties["schema_version"], 0)
assert.Equal(t, map[string]any{"process_person_profile": false}, event.Options)
assert.Equal(t, map[string]any{"namespace": "hashed-tenant"}, sent.batch[1].Properties["$groups"])
}
func TestPreviewIsTheEventOnTheWire(t *testing.T) {
c := newCollector(t, "")
client, err := telemetry.New(c.listener.URL, "community-write-key")
require.NoError(t, err)
require.NoError(t, client.Send(heartbeat()))
require.NoError(t, client.Close())
events := c.events()
require.Len(t, events, 1)
wire := events[0]
preview := heartbeat().Preview()
assert.Equal(t, wire.Event, preview.Event)
assert.Equal(t, wire.DistinctID, preview.DistinctID)
assert.Equal(t, wire.Options, preview.Options)
encoded, err := json.Marshal(preview.Properties)
require.NoError(t, err)
var properties map[string]any
require.NoError(t, json.Unmarshal(encoded, &properties))
for name, value := range properties {
assert.Equal(t, value, wire.Properties[name], "property %s", name)
}
for name := range wire.Properties {
if _, ours := properties[name]; !ours {
assert.Contains(t, telemetry.ClientProperties, name, "posthog-go added a property missing from ClientProperties")
}
}
}
func TestTelemetryPageListsEveryClientProperty(t *testing.T) {
page, err := os.ReadFile("../../ui/apps/docs/src/pages/selfhosted/telemetry.mdx")
require.NoError(t, err)
for _, name := range telemetry.ClientProperties {
assert.Contains(t, string(page), "| `"+name+"` |", "the telemetry page does not list %s", name)
}
}
func TestClientLogsAnEventTheCollectorDrops(t *testing.T) {
hook := logtest.NewGlobal()
level := log.GetLevel()
log.SetLevel(log.DebugLevel)
t.Cleanup(func() {
log.SetLevel(level)
hook.Reset()
})
c := newCollector(t, "instance heartbeat")
client, err := telemetry.New(c.listener.URL, "community-write-key")
require.NoError(t, err)
require.NoError(t, client.Send(heartbeat()))
require.NoError(t, client.Close())
var dropped *log.Entry
for _, entry := range hook.AllEntries() {
if entry.Message == "the telemetry collector did not accept an event" {
dropped = entry
}
}
require.NotNil(t, dropped)
assert.Equal(t, log.DebugLevel, dropped.Level)
assert.Equal(t, "instance heartbeat", dropped.Data["event"])
}