test(observability): cover the telemetry success path

Adds a fake OTLP collector so NewTelemetryClient can be exercised against a
reachable endpoint: it builds all three providers and both ForceFlush and
Shutdown export and tear down without error. Previously only the
not-configured and unreachable-dial paths were covered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sergey Kozyrenko
2026-07-11 10:02:58 +07:00
parent 745c8ae6d6
commit d44a33f5dd
@@ -8,8 +8,37 @@ import (
"time"
"pentagi/pkg/config"
collogsv1 "go.opentelemetry.io/proto/otlp/collector/logs/v1"
colmetricsv1 "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
coltracev1 "go.opentelemetry.io/proto/otlp/collector/trace/v1"
"google.golang.org/grpc"
)
type fakeLogsCollector struct {
collogsv1.UnimplementedLogsServiceServer
}
func (fakeLogsCollector) Export(context.Context, *collogsv1.ExportLogsServiceRequest) (*collogsv1.ExportLogsServiceResponse, error) {
return &collogsv1.ExportLogsServiceResponse{}, nil
}
type fakeMetricsCollector struct {
colmetricsv1.UnimplementedMetricsServiceServer
}
func (fakeMetricsCollector) Export(context.Context, *colmetricsv1.ExportMetricsServiceRequest) (*colmetricsv1.ExportMetricsServiceResponse, error) {
return &colmetricsv1.ExportMetricsServiceResponse{}, nil
}
type fakeTraceCollector struct {
coltracev1.UnimplementedTraceServiceServer
}
func (fakeTraceCollector) Export(context.Context, *coltracev1.ExportTraceServiceRequest) (*coltracev1.ExportTraceServiceResponse, error) {
return &coltracev1.ExportTraceServiceResponse{}, nil
}
func TestNewTelemetryClient_EmptyEndpointReturnsErrNotConfigured(t *testing.T) {
_, err := NewTelemetryClient(context.Background(), &config.Config{TelemetryEndpoint: ""})
if !errors.Is(err, ErrNotConfigured) {
@@ -17,6 +46,36 @@ func TestNewTelemetryClient_EmptyEndpointReturnsErrNotConfigured(t *testing.T) {
}
}
func TestNewTelemetryClient_SuccessPathExportsAndShutsDown(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
srv := grpc.NewServer()
collogsv1.RegisterLogsServiceServer(srv, fakeLogsCollector{})
colmetricsv1.RegisterMetricsServiceServer(srv, fakeMetricsCollector{})
coltracev1.RegisterTraceServiceServer(srv, fakeTraceCollector{})
go func() { _ = srv.Serve(ln) }()
defer srv.Stop()
client, err := NewTelemetryClient(context.Background(), &config.Config{TelemetryEndpoint: ln.Addr().String()})
if err != nil {
t.Fatalf("NewTelemetryClient: %v", err)
}
if client.Logger() == nil || client.Tracer() == nil || client.Meter() == nil {
t.Fatal("a telemetry provider is nil")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := client.ForceFlush(ctx); err != nil {
t.Fatalf("ForceFlush: %v", err)
}
if err := client.Shutdown(ctx); err != nil {
t.Fatalf("Shutdown: %v", err)
}
}
// H4 guard: a set-but-unreachable collector must not hang startup. The endpoint
// accepts the TCP connection but never completes the gRPC handshake, so a
// WithBlock dial would wait forever without the internal DefaultDialTimeout that