Files
onix/core/module/handler/step_metrics_test.go

239 lines
7.6 KiB
Go

package handler
import (
"context"
"sync"
"testing"
"go.opentelemetry.io/otel/metric"
"github.com/beckn-one/beckn-onix/pkg/telemetry"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetStepMetrics_Success(t *testing.T) {
ctx := context.Background()
// Initialize telemetry provider first
provider, err := telemetry.NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
// Test getting step metrics
metrics, err := GetStepMetrics(ctx)
require.NoError(t, err, "GetStepMetrics() should not return error")
require.NotNil(t, metrics, "GetStepMetrics() should return non-nil metrics")
// Verify all metric instruments are initialized
assert.NotNil(t, metrics.StepExecutionDuration, "StepExecutionDuration should be initialized")
assert.NotNil(t, metrics.StepExecutionTotal, "StepExecutionTotal should be initialized")
assert.NotNil(t, metrics.StepErrorsTotal, "StepErrorsTotal should be initialized")
}
func TestGetStepMetrics_ConcurrentAccess(t *testing.T) {
ctx := context.Background()
// Initialize telemetry provider first
provider, err := telemetry.NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
// Test that GetStepMetrics is safe for concurrent access
// and returns the same instance (singleton pattern)
metrics1, err1 := GetStepMetrics(ctx)
require.NoError(t, err1)
require.NotNil(t, metrics1)
metrics2, err2 := GetStepMetrics(ctx)
require.NoError(t, err2)
require.NotNil(t, metrics2)
// Should return the same instance
assert.Equal(t, metrics1, metrics2, "GetStepMetrics should return the same instance")
}
func TestGetStepMetrics_WithoutProvider(t *testing.T) {
ctx := context.Background()
// Test getting step metrics without initializing provider
// This should still work but may not have a valid meter provider
metrics, err := GetStepMetrics(ctx)
// Note: This might succeed or fail depending on OTel's default behavior
// We're just checking it doesn't panic
if err != nil {
t.Logf("GetStepMetrics returned error (expected if no provider): %v", err)
} else {
assert.NotNil(t, metrics, "Metrics should be returned even without explicit provider")
}
}
func TestStepMetrics_Instruments(t *testing.T) {
ctx := context.Background()
// Initialize telemetry provider
provider, err := telemetry.NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
// Get step metrics
metrics, err := GetStepMetrics(ctx)
require.NoError(t, err)
require.NotNil(t, metrics)
// Test that we can record metrics (this tests the instruments are functional)
// Note: We can't easily verify the metrics were recorded without querying the exporter,
// but we can verify the instruments are not nil and can be called without panicking
// Test StepExecutionDuration
require.NotPanics(t, func() {
metrics.StepExecutionDuration.Record(ctx, 0.5,
metric.WithAttributes(telemetry.AttrStep.String("test-step"), telemetry.AttrModule.String("test-module")))
}, "StepExecutionDuration.Record should not panic")
// Test StepExecutionTotal
require.NotPanics(t, func() {
metrics.StepExecutionTotal.Add(ctx, 1,
metric.WithAttributes(telemetry.AttrStep.String("test-step"), telemetry.AttrModule.String("test-module")))
}, "StepExecutionTotal.Add should not panic")
// Test StepErrorsTotal
require.NotPanics(t, func() {
metrics.StepErrorsTotal.Add(ctx, 1,
metric.WithAttributes(telemetry.AttrStep.String("test-step"), telemetry.AttrModule.String("test-module")))
}, "StepErrorsTotal.Add should not panic")
// MeterProvider is set by NewTestProvider; metrics are recorded via OTel SDK
assert.NotNil(t, provider.MeterProvider, "MeterProvider should be set")
}
func TestStepMetrics_MultipleCalls(t *testing.T) {
ctx := context.Background()
// Initialize telemetry provider
provider, err := telemetry.NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
// Call GetStepMetrics multiple times
for i := 0; i < 10; i++ {
metrics, err := GetStepMetrics(ctx)
require.NoError(t, err, "GetStepMetrics should succeed on call %d", i)
require.NotNil(t, metrics, "GetStepMetrics should return non-nil on call %d", i)
assert.NotNil(t, metrics.StepExecutionDuration, "StepExecutionDuration should be initialized")
assert.NotNil(t, metrics.StepExecutionTotal, "StepExecutionTotal should be initialized")
assert.NotNil(t, metrics.StepErrorsTotal, "StepErrorsTotal should be initialized")
}
}
func TestStepMetrics_RecordWithDifferentAttributes(t *testing.T) {
ctx := context.Background()
provider, err := telemetry.NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
metrics, err := GetStepMetrics(ctx)
require.NoError(t, err)
require.NotNil(t, metrics)
attrsList := []struct {
step string
module string
}{
{"test-step", "test-module"},
{"", "module-only"},
{"step-only", ""},
{"", ""},
{"long-step-name-with-many-parts", "long-module-name"},
}
for _, a := range attrsList {
attrs := metric.WithAttributes(
telemetry.AttrStep.String(a.step),
telemetry.AttrModule.String(a.module),
)
require.NotPanics(t, func() {
metrics.StepExecutionDuration.Record(ctx, 0.01, attrs)
metrics.StepExecutionTotal.Add(ctx, 1, attrs)
metrics.StepErrorsTotal.Add(ctx, 0, attrs)
}, "Recording with step=%q module=%q should not panic", a.step, a.module)
}
}
func TestStepMetrics_DurationValues(t *testing.T) {
ctx := context.Background()
provider, err := telemetry.NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
metrics, err := GetStepMetrics(ctx)
require.NoError(t, err)
require.NotNil(t, metrics)
attrs := metric.WithAttributes(
telemetry.AttrStep.String("test-step"),
telemetry.AttrModule.String("test-module"),
)
durations := []float64{0, 0.0005, 0.001, 0.01, 0.1, 0.5}
for _, d := range durations {
d := d
require.NotPanics(t, func() {
metrics.StepExecutionDuration.Record(ctx, d, attrs)
}, "StepExecutionDuration.Record(%.4f) should not panic", d)
}
}
func TestStepMetrics_ConcurrentRecord(t *testing.T) {
ctx := context.Background()
provider, err := telemetry.NewTestProvider(ctx)
require.NoError(t, err)
defer provider.Shutdown(context.Background())
metrics, err := GetStepMetrics(ctx)
require.NoError(t, err)
require.NotNil(t, metrics)
attrs := metric.WithAttributes(
telemetry.AttrStep.String("concurrent-step"),
telemetry.AttrModule.String("concurrent-module"),
)
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
wg.Add(1)
go func() {
defer wg.Done()
metrics.StepExecutionDuration.Record(ctx, 0.05, attrs)
metrics.StepExecutionTotal.Add(ctx, 1, attrs)
metrics.StepErrorsTotal.Add(ctx, 0, attrs)
}()
}
wg.Wait()
}
func TestStepMetrics_WithTraceProvider(t *testing.T) {
ctx := context.Background()
provider, sr, err := telemetry.NewTestProviderWithTrace(ctx)
require.NoError(t, err)
require.NotNil(t, provider)
require.NotNil(t, sr)
defer provider.Shutdown(ctx)
metrics, err := GetStepMetrics(ctx)
require.NoError(t, err)
require.NotNil(t, metrics)
assert.NotNil(t, provider.MeterProvider, "MeterProvider should be set")
assert.NotNil(t, provider.TraceProvider, "TraceProvider should be set")
attrs := metric.WithAttributes(
telemetry.AttrStep.String("trace-test-step"),
telemetry.AttrModule.String("trace-test-module"),
)
require.NotPanics(t, func() {
metrics.StepExecutionDuration.Record(ctx, 0.1, attrs)
metrics.StepExecutionTotal.Add(ctx, 1, attrs)
metrics.StepErrorsTotal.Add(ctx, 0, attrs)
}, "Step metrics should work when trace provider is also set")
}