Files
goplt/internal/observability/tracer_test.go
0x1d 5fdbb729bd
Some checks failed
CI / Test (pull_request) Failing after 17s
CI / Lint (pull_request) Failing after 18s
CI / Build (pull_request) Successful in 12s
CI / Format Check (pull_request) Successful in 2s
test: add comprehensive tests for all Epic 1 stories
Story 1.2: Database Layer
- Test database client creation, connection, ping, and close
- Test connection pooling configuration
- Tests skip if database is not available (short mode)

Story 1.3: Health Monitoring and Metrics
- Test health registry registration and checking
- Test database health checker
- Test liveness and readiness checks
- Test metrics creation, middleware, and handler
- Test Prometheus metrics endpoint

Story 1.4: Error Handling and Error Bus
- Test channel-based error bus creation
- Test error publishing with context
- Test nil error handling
- Test channel full scenario
- Test graceful shutdown
- Fix Close() method to handle multiple calls safely

Story 1.5: HTTP Server and Middleware
- Test server creation with all middleware
- Test request ID middleware
- Test logging middleware
- Test panic recovery middleware
- Test CORS middleware
- Test timeout middleware
- Test health and metrics endpoints
- Test server shutdown

Story 1.6: OpenTelemetry Tracing
- Test tracer initialization (enabled/disabled)
- Test development and production modes
- Test OTLP exporter configuration
- Test graceful shutdown
- Test no-op tracer provider

All tests follow Go testing best practices:
- Table-driven tests where appropriate
- Parallel execution
- Proper mocking of interfaces
- Skip tests requiring external dependencies in short mode
2025-11-05 21:05:36 +01:00

187 lines
3.9 KiB
Go

package observability
import (
"context"
"testing"
"time"
"go.opentelemetry.io/otel/trace"
"go.opentelemetry.io/otel/trace/noop"
)
func TestInitTracer_Disabled(t *testing.T) {
t.Parallel()
cfg := Config{
Enabled: false,
}
ctx := context.Background()
tp, err := InitTracer(ctx, cfg)
if err != nil {
t.Fatalf("InitTracer failed: %v", err)
}
if tp == nil {
t.Fatal("Expected tracer provider, got nil")
}
}
func TestInitTracer_DevelopmentMode(t *testing.T) {
t.Parallel()
cfg := Config{
Enabled: true,
ServiceName: "test-service",
ServiceVersion: "1.0.0",
Environment: "development",
}
ctx := context.Background()
tp, err := InitTracer(ctx, cfg)
if err != nil {
t.Fatalf("InitTracer failed: %v", err)
}
if tp == nil {
t.Fatal("Expected tracer provider, got nil")
}
// Verify it's not a no-op tracer
if _, ok := tp.(*noop.TracerProvider); ok {
t.Error("Expected real tracer provider in development mode")
}
// Clean up
if err := ShutdownTracer(ctx, tp); err != nil {
t.Logf("Failed to shutdown tracer: %v", err)
}
}
func TestInitTracer_ProductionMode_WithOTLP(t *testing.T) {
t.Parallel()
cfg := Config{
Enabled: true,
ServiceName: "test-service",
ServiceVersion: "1.0.0",
Environment: "production",
OTLPEndpoint: "http://localhost:4318",
}
ctx := context.Background()
tp, err := InitTracer(ctx, cfg)
if err != nil {
// OTLP endpoint might not be available in tests, that's okay
t.Skipf("Skipping test - OTLP endpoint not available: %v", err)
}
if tp == nil {
t.Fatal("Expected tracer provider, got nil")
}
// Clean up
if err := ShutdownTracer(ctx, tp); err != nil {
t.Logf("Failed to shutdown tracer: %v", err)
}
}
func TestInitTracer_ProductionMode_WithoutOTLP(t *testing.T) {
t.Parallel()
cfg := Config{
Enabled: true,
ServiceName: "test-service",
ServiceVersion: "1.0.0",
Environment: "production",
OTLPEndpoint: "",
}
ctx := context.Background()
tp, err := InitTracer(ctx, cfg)
if err != nil {
t.Fatalf("InitTracer failed: %v", err)
}
if tp == nil {
t.Fatal("Expected tracer provider, got nil")
}
// Clean up
if err := ShutdownTracer(ctx, tp); err != nil {
t.Logf("Failed to shutdown tracer: %v", err)
}
}
func TestShutdownTracer(t *testing.T) {
t.Parallel()
cfg := Config{
Enabled: true,
ServiceName: "test-service",
ServiceVersion: "1.0.0",
Environment: "development",
}
ctx := context.Background()
tp, err := InitTracer(ctx, cfg)
if err != nil {
t.Fatalf("InitTracer failed: %v", err)
}
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := ShutdownTracer(shutdownCtx, tp); err != nil {
t.Errorf("ShutdownTracer failed: %v", err)
}
}
func TestShutdownTracer_NoopTracer(t *testing.T) {
t.Parallel()
tp := noop.NewTracerProvider()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Should not fail for no-op tracer
if err := ShutdownTracer(ctx, tp); err != nil {
t.Errorf("ShutdownTracer should handle no-op tracer gracefully: %v", err)
}
}
func TestInitTracer_InvalidResource(t *testing.T) {
t.Parallel()
// This test would require invalid resource configuration
// Since resource.New doesn't have easy ways to fail, we'll skip this
// In practice, resource.New should always succeed with valid inputs
t.Skip("Skipping - resource.New doesn't easily fail with test inputs")
}
func TestTracerProvider_ImplementsInterface(t *testing.T) {
t.Parallel()
cfg := Config{
Enabled: true,
ServiceName: "test-service",
ServiceVersion: "1.0.0",
Environment: "development",
}
ctx := context.Background()
tp, err := InitTracer(ctx, cfg)
if err != nil {
t.Skipf("Skipping test - tracer init failed: %v", err)
}
// Verify it implements the interface
var _ trace.TracerProvider = tp
// Clean up
if err := ShutdownTracer(ctx, tp); err != nil {
t.Logf("Failed to shutdown tracer: %v", err)
}
}