142 lines
4.3 KiB
Go
142 lines
4.3 KiB
Go
package telemetry
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/aoagents/agent-orchestrator/backend/internal/domain"
|
|
"github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
|
)
|
|
|
|
func TestPostHogSinkCapturesEvent(t *testing.T) {
|
|
requests := make(chan map[string]any, 1)
|
|
sink, err := NewPostHogSink(t.TempDir(), "phc_test", "https://us.i.posthog.com", roundTripClient(func(req *http.Request) (*http.Response, error) {
|
|
defer req.Body.Close()
|
|
var body map[string]any
|
|
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
|
return nil, err
|
|
}
|
|
requests <- body
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Header: make(http.Header),
|
|
Body: http.NoBody,
|
|
}, nil
|
|
}), nil)
|
|
if err != nil {
|
|
t.Fatalf("NewPostHogSink: %v", err)
|
|
}
|
|
|
|
projectID := domain.ProjectID("proj-1")
|
|
sessionID := domain.SessionID("sess-1")
|
|
sink.Emit(context.Background(), ports.TelemetryEvent{
|
|
Name: "ao.session.spawned",
|
|
Source: "session_service",
|
|
OccurredAt: time.Unix(1700000000, 0).UTC(),
|
|
Level: ports.TelemetryLevelInfo,
|
|
ProjectID: &projectID,
|
|
SessionID: &sessionID,
|
|
RequestID: "req-1",
|
|
Payload: map[string]any{
|
|
"kind": "worker",
|
|
},
|
|
})
|
|
if err := sink.Close(context.Background()); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
select {
|
|
case req := <-requests:
|
|
if got := req["event"]; got != "ao.session.spawned" {
|
|
t.Fatalf("event = %#v, want ao.session.spawned", got)
|
|
}
|
|
props, ok := req["properties"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("properties type = %T, want map[string]any", req["properties"])
|
|
}
|
|
if props["kind"] != "worker" {
|
|
t.Fatalf("properties.kind = %#v, want worker", props["kind"])
|
|
}
|
|
if props["project_id_hash"] == "" || props["session_id_hash"] == "" {
|
|
t.Fatalf("hashed ids missing from properties: %#v", props)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("PostHog sink did not send request")
|
|
}
|
|
}
|
|
|
|
func TestPostHogSinkSanitizesPayloads(t *testing.T) {
|
|
requests := make(chan map[string]any, 1)
|
|
sink, err := NewPostHogSink(t.TempDir(), "phc_test", "https://us.i.posthog.com", roundTripClient(func(req *http.Request) (*http.Response, error) {
|
|
defer req.Body.Close()
|
|
var body map[string]any
|
|
if err := json.NewDecoder(req.Body).Decode(&body); err != nil {
|
|
return nil, err
|
|
}
|
|
requests <- body
|
|
return &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Header: make(http.Header),
|
|
Body: http.NoBody,
|
|
}, nil
|
|
}), nil)
|
|
if err != nil {
|
|
t.Fatalf("NewPostHogSink: %v", err)
|
|
}
|
|
|
|
sink.Emit(context.Background(), ports.TelemetryEvent{
|
|
Name: "ao.daemon.panic",
|
|
Source: "http",
|
|
OccurredAt: time.Unix(1700000000, 0).UTC(),
|
|
Level: ports.TelemetryLevelError,
|
|
Payload: map[string]any{
|
|
"component": "httpd",
|
|
"operation": "http_request_panic",
|
|
"method": http.MethodGet,
|
|
"path": "/api/v1/sessions/demo",
|
|
"panic_kind": "error",
|
|
"fingerprint": "abc123",
|
|
"stack_fingerprint": "def456",
|
|
"panic": "open /Users/name/private: no such file",
|
|
"stack": "stack trace with local path",
|
|
},
|
|
})
|
|
if err := sink.Close(context.Background()); err != nil {
|
|
t.Fatalf("Close: %v", err)
|
|
}
|
|
|
|
select {
|
|
case req := <-requests:
|
|
props, ok := req["properties"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("properties type = %T, want map[string]any", req["properties"])
|
|
}
|
|
if props["component"] != "httpd" || props["operation"] != "http_request_panic" {
|
|
t.Fatalf("sanitized properties = %#v, want allowlisted metadata", props)
|
|
}
|
|
if props["method"] != http.MethodGet || props["path"] != "/api/v1/sessions/demo" || props["panic_kind"] != "error" {
|
|
t.Fatalf("sanitized properties = %#v, want allowlisted fields", props)
|
|
}
|
|
if props["fingerprint"] != "abc123" || props["stack_fingerprint"] != "def456" {
|
|
t.Fatalf("sanitized properties = %#v, want exported fingerprints", props)
|
|
}
|
|
if _, ok := props["panic"]; ok {
|
|
t.Fatalf("panic property should be dropped: %#v", props)
|
|
}
|
|
if _, ok := props["stack"]; ok {
|
|
t.Fatalf("stack property should be dropped: %#v", props)
|
|
}
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("PostHog sink did not send request")
|
|
}
|
|
}
|
|
|
|
type roundTripClient func(*http.Request) (*http.Response, error)
|
|
|
|
func (f roundTripClient) Do(req *http.Request) (*http.Response, error) { return f(req) }
|
|
|
|
var _ postHogClient = roundTripClient(nil)
|