package telemetry import ( "context" "net/http" "net/http/httptest" "os" "testing" "time" ) func TestDefaultConfig(t *testing.T) { // Clear env vars that might affect defaults _ = os.Unsetenv("OTEL_EXPORTER_OTLP_ENDPOINT") _ = os.Unsetenv("OTEL_SERVICE_NAME") _ = os.Unsetenv("OTEL_SERVICE_VERSION") _ = os.Unsetenv("OTEL_SERVICE_NAMESPACE") _ = os.Unsetenv("OTEL_ENABLED") cfg := DefaultConfig() if cfg.Endpoint != "otel-collector.observability.svc:4317" { t.Errorf("expected default endpoint, got %s", cfg.Endpoint) } if cfg.ServiceName != "rdev-api" { t.Errorf("expected default service name, got %s", cfg.ServiceName) } if cfg.ServiceVersion != "unknown" { t.Errorf("expected default service version, got %s", cfg.ServiceVersion) } if cfg.ServiceNamespace != "rdev" { t.Errorf("expected default service namespace, got %s", cfg.ServiceNamespace) } if !cfg.Enabled { t.Error("expected telemetry enabled by default") } } func TestDefaultConfigWithEnv(t *testing.T) { // Set custom env vars _ = os.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "custom-collector:4317") _ = os.Setenv("OTEL_SERVICE_NAME", "custom-service") _ = os.Setenv("OTEL_SERVICE_VERSION", "v1.2.3") _ = os.Setenv("OTEL_SERVICE_NAMESPACE", "custom-ns") _ = os.Setenv("OTEL_ENABLED", "false") defer func() { _ = os.Unsetenv("OTEL_EXPORTER_OTLP_ENDPOINT") _ = os.Unsetenv("OTEL_SERVICE_NAME") _ = os.Unsetenv("OTEL_SERVICE_VERSION") _ = os.Unsetenv("OTEL_SERVICE_NAMESPACE") _ = os.Unsetenv("OTEL_ENABLED") }() cfg := DefaultConfig() if cfg.Endpoint != "custom-collector:4317" { t.Errorf("expected custom endpoint, got %s", cfg.Endpoint) } if cfg.ServiceName != "custom-service" { t.Errorf("expected custom service name, got %s", cfg.ServiceName) } if cfg.ServiceVersion != "v1.2.3" { t.Errorf("expected custom service version, got %s", cfg.ServiceVersion) } if cfg.ServiceNamespace != "custom-ns" { t.Errorf("expected custom service namespace, got %s", cfg.ServiceNamespace) } if cfg.Enabled { t.Error("expected telemetry disabled") } } func TestNewTelemetryDisabled(t *testing.T) { cfg := Config{ Enabled: false, ServiceName: "test-service", } tel, err := New(context.Background(), cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } if tel == nil { t.Fatal("expected telemetry instance") } // Verify tracer is available (noop) if tel.Tracer() == nil { t.Error("expected noop tracer") } // Shutdown should be safe if err := tel.Shutdown(context.Background()); err != nil { t.Errorf("unexpected shutdown error: %v", err) } } func TestNewTelemetryWithBadEndpoint(t *testing.T) { // This test verifies that creation doesn't fail even with unreachable endpoint // The actual connection happens asynchronously during export cfg := Config{ Enabled: true, Endpoint: "localhost:99999", ServiceName: "test-service", Insecure: true, } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() tel, err := New(ctx, cfg) if err != nil { t.Fatalf("unexpected error creating telemetry: %v", err) } defer func() { _ = tel.Shutdown(context.Background()) }() // Should be able to create spans even if collector is unreachable _, span := tel.StartSpan(context.Background(), "test-span") span.End() } func TestStartSpan(t *testing.T) { cfg := Config{ Enabled: false, ServiceName: "test-service", } tel, err := New(context.Background(), cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } defer func() { _ = tel.Shutdown(context.Background()) }() ctx, span := tel.StartSpan(context.Background(), "test-operation") if span == nil { t.Error("expected span") } if ctx == nil { t.Error("expected context") } span.End() } func TestGetEnvBool(t *testing.T) { tests := []struct { value string expected bool }{ {"true", true}, {"TRUE", true}, {"True", true}, {"1", true}, {"yes", true}, {"YES", true}, {"false", false}, {"FALSE", false}, {"0", false}, {"no", false}, {"anything", false}, } for _, tt := range tests { t.Run(tt.value, func(t *testing.T) { os.Setenv("TEST_BOOL", tt.value) defer os.Unsetenv("TEST_BOOL") result := getEnvBool("TEST_BOOL", false) if result != tt.expected { t.Errorf("getEnvBool(%q) = %v, want %v", tt.value, result, tt.expected) } }) } } func TestNormalizePath(t *testing.T) { tests := []struct { input string expected string }{ // Keys {"/keys/550e8400-e29b-41d4-a716-446655440000", "/keys/{id}"}, {"/keys", "/keys"}, // Projects {"/projects/pantheon", "/projects/{id}"}, {"/projects/pantheon/claude", "/projects/{id}/claude"}, {"/projects/aeries/shell", "/projects/{id}/shell"}, {"/projects/test-123/events", "/projects/{id}/events"}, // Claude config {"/projects/pantheon/claude-config/commands/deploy", "/projects/{id}/claude-config/commands/{name}"}, {"/projects/pantheon/claude-config/skills/go-testing", "/projects/{id}/claude-config/skills/{name}"}, {"/projects/pantheon/claude-config/agents/reviewer", "/projects/{id}/claude-config/agents/{name}"}, {"/projects/pantheon/claude-config/commands", "/projects/{id}/claude-config/commands"}, {"/projects/pantheon/claude-config", "/projects/{id}/claude-config"}, // Unchanged {"/health", "/health"}, {"/ready", "/ready"}, {"/metrics", "/metrics"}, {"/docs", "/docs"}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { result := normalizePath(tt.input) if result != tt.expected { t.Errorf("normalizePath(%q) = %q, want %q", tt.input, result, tt.expected) } }) } } func TestMiddleware(t *testing.T) { // Create a simple handler handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("OK")) }) // Wrap with telemetry middleware wrapped := Middleware("test-service")(handler) // Create test request req := httptest.NewRequest(http.MethodGet, "/health", nil) req.Header.Set("X-Real-IP", "192.168.1.1") rec := httptest.NewRecorder() // Execute wrapped.ServeHTTP(rec, req) // Verify response if rec.Code != http.StatusOK { t.Errorf("expected status 200, got %d", rec.Code) } if rec.Body.String() != "OK" { t.Errorf("expected body OK, got %s", rec.Body.String()) } } func TestMiddlewareWithError(t *testing.T) { // Create a handler that returns an error handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte("error")) }) wrapped := Middleware("test-service")(handler) req := httptest.NewRequest(http.MethodPost, "/projects/test/claude", nil) rec := httptest.NewRecorder() wrapped.ServeHTTP(rec, req) if rec.Code != http.StatusInternalServerError { t.Errorf("expected status 500, got %d", rec.Code) } } func TestResponseWriter(t *testing.T) { rec := httptest.NewRecorder() rw := &responseWriter{ResponseWriter: rec, statusCode: http.StatusOK} // Test WriteHeader rw.WriteHeader(http.StatusCreated) if rw.statusCode != http.StatusCreated { t.Errorf("expected status 201, got %d", rw.statusCode) } // Test Write n, err := rw.Write([]byte("test")) if err != nil { t.Errorf("unexpected error: %v", err) } if n != 4 { t.Errorf("expected 4 bytes written, got %d", n) } if rw.bytesWritten != 4 { t.Errorf("expected 4 bytes tracked, got %d", rw.bytesWritten) } // Test Unwrap if rw.Unwrap() != rec { t.Error("Unwrap should return original ResponseWriter") } } func TestGetScheme(t *testing.T) { tests := []struct { name string setup func(*http.Request) expected string }{ { name: "default http", setup: func(r *http.Request) {}, expected: "http", }, { name: "x-forwarded-proto https", setup: func(r *http.Request) { r.Header.Set("X-Forwarded-Proto", "https") }, expected: "https", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/test", nil) tt.setup(req) result := getScheme(req) if result != tt.expected { t.Errorf("getScheme() = %q, want %q", result, tt.expected) } }) } }