mirror of
https://github.com/lukaszraczylo/graphql-monitoring-proxy.git
synced 2026-10-12 05:32:36 +00:00
- tracing: validateEndpoint used fmt.Sscanf("%s:%d") which treats %s as
greedy, so every valid host:port endpoint failed and traced==nil disabled
tracing entirely. Validate with net.SplitHostPort (pure helper, port range
0-65535); test TestValidateEndpoint.
- proxy: createFasthttpClient used clientTimeout for every timeout slot,
ignoring the parsed CLIENT_READ_TIMEOUT/CLIENT_WRITE_TIMEOUT (silent
config loss, documented separately in README). Honour per-side read/write
timeouts, falling back to client timeout when unset.
- cache/memory: evictToFreeMemory capped candidates at maxCacheSize/5 and
stopped at the subset end, so a large write could leave the cache over
its memory limit. Collect all entries and sort.Slice (correct + O(n log n)
vs O(cap^2)), evict until the target is met or the cache is drained.
- cache/redis: the Redis cache client was never closed at shutdown (unlike
the metrics aggregator). Add RedisConfig.Close, CacheWrapper.Close, a
cache.Shutdown and register it with the ShutdownManager.
148 lines
4.7 KiB
Go
148 lines
4.7 KiB
Go
package tracing
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/propagation"
|
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
|
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
|
"go.opentelemetry.io/otel/trace/noop"
|
|
)
|
|
|
|
// TestNewTracing_NilContext covers the nil context early-return branch (line 34-36).
|
|
func TestNewTracing_NilContext_ReturnsError(t *testing.T) {
|
|
_, err := NewTracing(nil, "localhost:4317") //nolint:staticcheck // SA1012: intentional nil to test the error branch
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "context cannot be nil")
|
|
}
|
|
|
|
// TestNewTracing_InvalidEndpointFormats covers endpoint validation branches.
|
|
// validateEndpoint rejects missing/host-only endpoints and out-of-range ports.
|
|
func TestNewTracing_InvalidEndpointFormats_ReturnsError(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
endpoint string
|
|
}{
|
|
{name: "no port separator", endpoint: "localhost"},
|
|
{name: "port over max", endpoint: "localhost:999999"},
|
|
{name: "plain hostname only", endpoint: "myhost"},
|
|
{name: "just a number", endpoint: "12345"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := NewTracing(context.Background(), tt.endpoint)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "invalid endpoint format")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestShutdown_WithRealProvider covers the non-nil tracerProvider shutdown path (line 133).
|
|
func TestShutdown_WithRealProvider_NoError(t *testing.T) {
|
|
// Use in-memory exporter so no network needed.
|
|
exporter := tracetest.NewInMemoryExporter()
|
|
tp := sdktrace.NewTracerProvider(
|
|
sdktrace.WithSyncer(exporter),
|
|
)
|
|
ts := &TracingSetup{
|
|
tracerProvider: tp,
|
|
tracer: tp.Tracer("shutdown-test"),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
err := ts.Shutdown(ctx)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// TestStartSpan_WithRealTracer covers StartSpan with a real (noop) tracer — the non-nil path.
|
|
func TestStartSpan_WithRealTracer_ReturnsSpan(t *testing.T) {
|
|
tp := noop.NewTracerProvider()
|
|
ts := &TracingSetup{
|
|
tracer: tp.Tracer("start-span-test"),
|
|
}
|
|
ctx := context.Background()
|
|
span, newCtx := ts.StartSpan(ctx, "my-operation")
|
|
assert.NotNil(t, span)
|
|
assert.NotNil(t, newCtx)
|
|
span.End()
|
|
}
|
|
|
|
// TestStartSpanWithAttributes_WithRealTracer covers the non-nil tracer path with attrs.
|
|
func TestStartSpanWithAttributes_WithRealTracer_RecordsSpan(t *testing.T) {
|
|
exporter := tracetest.NewInMemoryExporter()
|
|
tp := sdktrace.NewTracerProvider(
|
|
sdktrace.WithSyncer(exporter),
|
|
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
|
)
|
|
ts := &TracingSetup{
|
|
tracerProvider: tp,
|
|
tracer: tp.Tracer("attr-test"),
|
|
}
|
|
|
|
ctx := context.Background()
|
|
attrs := map[string]string{
|
|
"user.id": "u-42",
|
|
"operation": "query",
|
|
}
|
|
span, newCtx := ts.StartSpanWithAttributes(ctx, "graphql-query", attrs)
|
|
require.NotNil(t, span)
|
|
require.NotNil(t, newCtx)
|
|
span.End()
|
|
|
|
spans := exporter.GetSpans()
|
|
require.Len(t, spans, 1)
|
|
assert.Equal(t, "graphql-query", spans[0].Name)
|
|
}
|
|
|
|
// TestExtractSpanContext_ValidTraceparent covers the valid span context branch (line 115-116).
|
|
// ExtractSpanContext uses otel.GetTextMapPropagator(); we must register the W3C
|
|
// TraceContext propagator before calling it (NewTracing normally does this).
|
|
func TestExtractSpanContext_ValidTraceparent_ReturnsValid(t *testing.T) {
|
|
otel.SetTextMapPropagator(propagation.TraceContext{})
|
|
|
|
tp := noop.NewTracerProvider()
|
|
ts := &TracingSetup{
|
|
tracer: tp.Tracer("extract-test"),
|
|
}
|
|
spanInfo := &TraceSpanInfo{
|
|
TraceParent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
|
|
}
|
|
spanCtx, err := ts.ExtractSpanContext(spanInfo)
|
|
require.NoError(t, err)
|
|
assert.True(t, spanCtx.IsValid())
|
|
}
|
|
|
|
// TestValidateEndpoint checks the extracted endpoint validator. Regression for
|
|
// the old fmt.Sscanf("%s:%d") which rejected every valid "host:port"
|
|
// endpoint (crushing tracing into permanent no-op).
|
|
func TestValidateEndpoint(t *testing.T) {
|
|
for _, tt := range []struct {
|
|
name string
|
|
endpoint string
|
|
wantErr bool
|
|
}{
|
|
{name: "valid host:port", endpoint: "localhost:4317", wantErr: false},
|
|
{name: "valid fqdn:port", endpoint: "otel.example.com:4317", wantErr: false},
|
|
{name: "valid high port", endpoint: "collector:65535", wantErr: false},
|
|
{name: "missing port", endpoint: "localhost", wantErr: true},
|
|
{name: "port too high", endpoint: "localhost:999999", wantErr: true},
|
|
{name: "non-numeric port", endpoint: "localhost:abc", wantErr: true},
|
|
{name: "bare number", endpoint: "12345", wantErr: true},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := validateEndpoint(tt.endpoint)
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "invalid endpoint format")
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|