rdev/internal/telemetry/telemetry_test.go
jordan 72d16929ca feat: Implement hexagonal architecture with services, webhooks, queue, and telemetry
Major refactoring to hexagonal (ports & adapters) architecture:

- Add service layer (apikey_service, project_service) for business logic
- Add webhook system with dispatcher and delivery tracking
- Add command queue with priority-based processing
- Add rate limiting with sliding window algorithm
- Add audit logging for command execution
- Add OpenTelemetry integration (traces, metrics, spans)
- Add circuit breaker for fault tolerance
- Add cached repository wrapper for performance
- Add comprehensive validation package
- Add Kubernetes client integration for pod management
- Add database migrations (allowed_ips, audit_log, rate_limiting, queue, webhooks)
- Add network policy and PodDisruptionBudget for k8s
- Remove legacy executor and projects/registry packages
- Untrack secrets.yaml (now managed via envault)
- Add coverage.out to .gitignore
- Add e2e test infrastructure with docker-compose
- Add comprehensive documentation (API, architecture, operations, plans)
- Add golangci-lint config and pre-commit hook

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 19:57:46 -07:00

320 lines
8.0 KiB
Go

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)
}
})
}
}