Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions pkg/telemetry/logexporters/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package logexporters

import (
"testing"
)

func TestExporterFactory(t *testing.T) {
tests := []struct {
name string
exporter string
protocol string
wantErr bool
errMessage string
}{
{name: "otlp alias is accepted", exporter: "otlp", protocol: "http", wantErr: false},
{name: "otlp-http alias is accepted", exporter: "otlp-http", protocol: "http", wantErr: false},
{name: "otlp-grpc alias is accepted", exporter: "otlp-grpc", protocol: "grpc", wantErr: false},
{name: "unsupported exporter", exporter: "prometheus", protocol: "http", wantErr: true, errMessage: "prometheus log exporter is unsupported"},
{name: "unsupported protocol", exporter: "otlp", protocol: "tcp", wantErr: true, errMessage: "unsupported protocol: tcp"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exp, err := ExporterFactory(tt.exporter, "localhost:4317", true, "", nil, tt.protocol)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
}
if tt.errMessage != "" && err.Error() != tt.errMessage {
t.Fatalf("expected error %q, got %q", tt.errMessage, err.Error())
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if exp == nil {
t.Fatalf("expected exporter, got nil")
}
})
}
}

func TestNewOTLP(t *testing.T) {
tests := []struct {
name string
insecure bool
urlpath string
headers map[string]string
protocol string
wantErr bool
}{
{name: "http insecure with options", insecure: true, urlpath: "/v1/logs", headers: map[string]string{"key": "value"}, protocol: "http"},
{name: "http secure", insecure: false, protocol: "http"},
{name: "grpc insecure", insecure: true, protocol: "grpc"},
{name: "grpc secure with headers", insecure: false, headers: map[string]string{"key": "value"}, protocol: "grpc"},
{name: "unsupported protocol", protocol: "udp", wantErr: true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exp, err := NewOTLP("localhost:4317", tt.insecure, tt.urlpath, tt.headers, tt.protocol)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if exp == nil {
t.Fatalf("expected exporter, got nil")
}
})
}
}
77 changes: 77 additions & 0 deletions pkg/telemetry/meterexporters/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package meterexporters

import (
"testing"
)

func TestExporterFactory(t *testing.T) {
tests := []struct {
name string
exporter string
protocol string
wantErr bool
errMessage string
}{
{name: "otlp alias is accepted", exporter: "otlp", protocol: "http", wantErr: false},
{name: "otlp-http alias is accepted", exporter: "otlp-http", protocol: "http", wantErr: false},
{name: "otlp-grpc alias is accepted", exporter: "otlp-grpc", protocol: "grpc", wantErr: false},
{name: "unsupported exporter", exporter: "prometheus", protocol: "http", wantErr: true, errMessage: "prometheus meter exporter is unsupported"},
{name: "unsupported protocol", exporter: "otlp", protocol: "tcp", wantErr: true, errMessage: "unsupported protocol: tcp"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exp, err := ExporterFactory(tt.exporter, "localhost:4317", true, "", nil, tt.protocol)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
}
if tt.errMessage != "" && err.Error() != tt.errMessage {
t.Fatalf("expected error %q, got %q", tt.errMessage, err.Error())
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if exp == nil {
t.Fatalf("expected exporter, got nil")
}
})
}
}

func TestNewOTLP(t *testing.T) {
tests := []struct {
name string
insecure bool
urlpath string
headers map[string]string
protocol string
wantErr bool
}{
{name: "http insecure with options", insecure: true, urlpath: "/v1/metrics", headers: map[string]string{"key": "value"}, protocol: "http"},
{name: "http secure", insecure: false, protocol: "http"},
{name: "grpc insecure", insecure: true, headers: map[string]string{"key": "value"}, protocol: "grpc"},
{name: "grpc secure", insecure: false, headers: map[string]string{"key": "value"}, protocol: "grpc"},
{name: "unsupported protocol", protocol: "udp", wantErr: true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exp, err := NewOTLP("localhost:4317", tt.insecure, tt.urlpath, tt.headers, tt.protocol)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if exp == nil {
t.Fatalf("expected exporter, got nil")
}
})
}
}
152 changes: 152 additions & 0 deletions pkg/telemetry/telemetry_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package telemetry

import (
"context"
"log/slog"
"testing"

semconv "go.opentelemetry.io/otel/semconv/v1.10.0"

"github.com/Permify/permify/pkg/telemetry/logexporters"
"github.com/Permify/permify/pkg/telemetry/tracerexporters"
)

func TestNewResource(t *testing.T) {
tests := []struct {
name string
serviceName string
wantService string
}{
{name: "empty falls back to permify", serviceName: "", wantService: "permify"},
{name: "whitespace falls back to permify", serviceName: " ", wantService: "permify"},
{name: "custom service name is honored", serviceName: "my-service", wantService: "my-service"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := newResource(tt.serviceName)
if res == nil {
t.Fatalf("expected resource, got nil")
}

var got string
for _, attr := range res.Attributes() {
if attr.Key == semconv.ServiceNameKey {
got = attr.Value.AsString()
}
}
if got != tt.wantService {
t.Fatalf("expected service name %q, got %q", tt.wantService, got)
}
})
}
}

func TestHandlerFactory(t *testing.T) {
tests := []struct {
name string
handler string
headers map[string]string
wantErr bool
}{
{name: "otlp", handler: "otlp", wantErr: false},
{name: "gcp missing project errors", handler: "gcp", headers: map[string]string{}, wantErr: true},
{name: "unsupported handler", handler: "prometheus", wantErr: true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h, err := HandlerFactory(tt.handler, "localhost:4317", true, "", tt.headers, "http", slog.LevelInfo, "svc")
if tt.wantErr {
if err == nil {
t.Fatalf("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatalf("expected handler, got nil")
}
})
}
}

func TestNewOTLPHandler(t *testing.T) {
h, err := NewOTLPHandler("localhost:4317", true, "/v1/logs", map[string]string{"key": "value"}, "http", slog.LevelDebug, "svc")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatalf("expected handler, got nil")
}
}

func TestNewGCPHandlerMissingProject(t *testing.T) {
if _, err := NewGCPHandler(map[string]string{}, slog.LevelInfo); err == nil {
t.Fatalf("expected error when project id is missing")
}
}

func TestNewLog(t *testing.T) {
exporter, err := logexporters.NewOTLP("localhost:4317", true, "", nil, "http")
if err != nil {
t.Fatalf("failed to build exporter: %v", err)
}
lp := NewLog(exporter, "svc")
if lp == nil {
t.Fatalf("expected logger provider, got nil")
}
_ = lp.Shutdown(context.Background())
}

func TestNewTracer(t *testing.T) {
tests := []struct {
name string
serviceName string
}{
{name: "custom service name", serviceName: "svc"},
{name: "empty service name", serviceName: ""},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exporter, err := tracerexporters.NewOTLP("localhost:4317", true, "", nil, "http")
if err != nil {
t.Fatalf("failed to build exporter: %v", err)
}
shutdown := NewTracer(exporter, tt.serviceName)
if shutdown == nil {
t.Fatalf("expected shutdown func, got nil")
}
if err := shutdown(context.Background()); err != nil {
t.Fatalf("unexpected shutdown error: %v", err)
}
})
}
}

// NewMeter is intentionally not unit-tested here: it starts process-global
// gopsutil host instrumentation via host.Start and installs a global meter
// provider, which is environment-dependent and flaky under `go test -race`.
// Its constituent pieces (the OTLP meter exporter and the meter helpers below)
// are covered instead.

func TestNewNoopMeter(t *testing.T) {
if NewNoopMeter() == nil {
t.Fatalf("expected noop meter, got nil")
}
}

func TestNewCounterAndHistogram(t *testing.T) {
meter := NewNoopMeter()

if NewCounter(meter, "test_counter", "a test counter") == nil {
t.Fatalf("expected counter, got nil")
}

if NewHistogram(meter, "test_histogram", "ms", "a test histogram") == nil {
t.Fatalf("expected histogram, got nil")
}
}
Loading
Loading