Documentation
¶
Overview ¶
Package testutil — OTLP gRPC test receiver.
FakeOTLP is an in-process OTLP gRPC server for verifying telemetry export in tests. It accepts trace, metric, and log Export RPCs, records the received payloads, and exposes counts (and the raw payloads) for assertions.
Usage:
r := testutil.NewFakeOTLP(t)
t.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://"+r.Addr())
shutdown, _, _ := observability.InitProvider(ctx, "svc", observability.ProviderConfig{...})
... emit spans/metrics/logs ...
_ = shutdown(ctx) // forces a final flush
assert.Equal(t, expected, r.SpanCount())
Always call shutdown before asserting counts — the OTel SDK batches exports and only drains on shutdown (or after the batch timeout).
NewFakeOTLPTLS is the TLS variant — it mints an ephemeral self-signed cert and exposes it as a PEM file via CertFile(t), which the test points the SDK at with OTEL_EXPORTER_OTLP_CERTIFICATE (the same custom-CA path a real operator uses for a private gateway). Each Export RPC's gRPC metadata is also captured (LastTraceHeaders / LastMetricHeaders / LastLogHeaders) for asserting auth-header propagation.
Index ¶
- Constants
- func AssertBodyContains(t *testing.T, rec *httptest.ResponseRecorder, expectedStatus int, ...)
- func AssertBodyEquals(t *testing.T, rec *httptest.ResponseRecorder, expectedStatus int, ...)
- func AssertJSONContains(t *testing.T, rec *httptest.ResponseRecorder, expectedStatus int, ...)
- func AssertJSONErrorResponse(t *testing.T, w *httptest.ResponseRecorder)
- func AssertJSONResponse(t *testing.T, rec *httptest.ResponseRecorder, expectedStatus int, expected any)
- func MakeExpiredJWT(t *testing.T, claims map[string]any) string
- func MakeJWT(t *testing.T, claims map[string]any) string
- func NewJetStream(t *testing.T) jetstream.JetStream
- func NewTestSchemaRegistry(tables ...*discovery.TableSchema) *discovery.SchemaRegistry
- func NopLogger() *slog.Logger
- func RunRoleMatrix(t *testing.T, cases []RoleCase, ...)
- type CapturedRequest
- type FakeOTLP
- func (r *FakeOTLP) Addr() string
- func (r *FakeOTLP) CertFile(t *testing.T) string
- func (r *FakeOTLP) LastLogHeaders() metadata.MD
- func (r *FakeOTLP) LastMetricHeaders() metadata.MD
- func (r *FakeOTLP) LastTraceHeaders() metadata.MD
- func (r *FakeOTLP) LogCount() int
- func (r *FakeOTLP) LogCountAtLevel(minSeverity int32) int
- func (r *FakeOTLP) MetricCount() int
- func (r *FakeOTLP) Reset()
- func (r *FakeOTLP) SpanCount() int
- type MockCache
- type MockConsumer
- type MockDeduplicator
- type MockJetStream
- func (m *MockJetStream) Consumer(ctx context.Context, stream, consumer string) (jetstream.Consumer, error)
- func (m *MockJetStream) PublishMsg(ctx context.Context, msg *nats.Msg, opts ...jetstream.PublishOpt) (*jetstream.PubAck, error)
- func (m *MockJetStream) Published() []*nats.Msg
- func (m *MockJetStream) Stream(ctx context.Context, name string) (jetstream.Stream, error)
- type MockJetStreamMsg
- func (m *MockJetStreamMsg) Ack() error
- func (m *MockJetStreamMsg) Data() []byte
- func (m *MockJetStreamMsg) DoubleAck(_ context.Context) error
- func (m *MockJetStreamMsg) Headers() nats.Header
- func (m *MockJetStreamMsg) InProgress() error
- func (m *MockJetStreamMsg) Metadata() (*jetstream.MsgMetadata, error)
- func (m *MockJetStreamMsg) Nak() error
- func (m *MockJetStreamMsg) NakWithDelay(_ time.Duration) error
- func (m *MockJetStreamMsg) Reply() string
- func (m *MockJetStreamMsg) Subject() string
- func (m *MockJetStreamMsg) Term() error
- func (m *MockJetStreamMsg) TermWithReason(string) error
- type MockPublisher
- type MockRoundTripper
- type MockStream
- func (m *MockStream) GetMsg(ctx context.Context, seq uint64, opts ...jetstream.GetMsgOpt) (*jetstream.RawStreamMsg, error)
- func (m *MockStream) Info(_ context.Context, _ ...jetstream.StreamInfoOpt) (*jetstream.StreamInfo, error)
- func (m *MockStream) Purge(_ context.Context, opts ...jetstream.StreamPurgeOpt) error
- type MockSubscriber
- type PublishedMessage
- type RoleCase
Constants ¶
const TestJWTSecret = "test-secret-at-least-32-chars-long!"
TestJWTSecret is a shared HMAC secret for test JWT generation.
Variables ¶
This section is empty.
Functions ¶
func AssertBodyContains ¶
func AssertBodyContains(t *testing.T, rec *httptest.ResponseRecorder, expectedStatus int, expectedSubstring string)
AssertBodyContains checks that rec has the expected status code and that the response body contains the expected substring.
func AssertBodyEquals ¶
func AssertBodyEquals(t *testing.T, rec *httptest.ResponseRecorder, expectedStatus int, expectedBody string)
AssertBodyEquals checks that rec has the expected status code and that the response body equals the expected string.
func AssertJSONContains ¶
func AssertJSONContains(t *testing.T, rec *httptest.ResponseRecorder, expectedStatus int, expectedKeys map[string]any)
AssertJSONContains checks that rec has the expected status code and that the response body contains the expected key-value pairs.
func AssertJSONErrorResponse ¶
func AssertJSONErrorResponse(t *testing.T, w *httptest.ResponseRecorder)
assertJSONErrorResponse verifies that a recorded response is a well-formed JSON error body with the exact headers writeJSONError guarantees: Content-Type: application/json and X-Content-Type-Options: nosniff. Pinned strict so any handler that bypasses writeJSONError and emits a different Content-Type fails the assertion across every call site.
func AssertJSONResponse ¶
func AssertJSONResponse(t *testing.T, rec *httptest.ResponseRecorder, expectedStatus int, expected any)
AssertJSONResponse checks that rec has the expected status code and that the response body, decoded as JSON, matches expected (compared as Go values).
func MakeExpiredJWT ¶
MakeExpiredJWT creates a signed HS256 JWT that expired 1 hour ago.
func MakeJWT ¶
MakeJWT creates a signed HS256 JWT with the given claims merged with standard exp/iat.
func NewJetStream ¶
NewJetStream starts an in-process NATS server with JetStream and returns a connected jetstream.JetStream handle. Cleanup is registered on t.
Lives in testutil so packages that need KV-backed tests (pipes.Store, policy.Store) get one shared embedded-server setup instead of each duplicating the natsserver / nats.Connect / jetstream.New dance.
func NewTestSchemaRegistry ¶
func NewTestSchemaRegistry(tables ...*discovery.TableSchema) *discovery.SchemaRegistry
NewTestSchemaRegistry creates a SchemaRegistry pre-loaded with the given table schemas. Does not require a real ClickHouse connection.
func NopLogger ¶
NopLogger returns a *slog.Logger that discards all output. Use in tests to suppress noisy log output from embedded NATS, etc.
func RunRoleMatrix ¶
func RunRoleMatrix(t *testing.T, cases []RoleCase, invoke func(t *testing.T, tc RoleCase) *httptest.ResponseRecorder)
RunRoleMatrix runs each case as a parallel subtest, building and invoking the handler through invoke and asserting the response matches WantForbidden. A forbidden case must return 403 with the standard JSON error envelope (`application/json`, `nosniff`, an `error` field); a permitted case must return neither 403 nor 404. invoke owns the resource/request wiring (and any panic recovery for handlers that reach a nil backend on the allowed path).
Types ¶
type CapturedRequest ¶
CapturedRequest is a frozen snapshot of an outgoing HTTP request.
type FakeOTLP ¶
type FakeOTLP struct {
// contains filtered or unexported fields
}
FakeOTLP is a single gRPC server that implements the trace, metric, and log Export RPCs and captures every received payload (and each request's gRPC metadata). Cleanup is registered on the *testing.T automatically.
func NewFakeOTLP ¶
NewFakeOTLP binds the receiver to 127.0.0.1 on a random port and starts serving plaintext gRPC. The server is stopped automatically when the test ends.
func NewFakeOTLPTLS ¶
NewFakeOTLPTLS is the TLS variant: mints an ephemeral self-signed cert (SAN 127.0.0.1) and exposes it as a PEM file via CertFile(t).
func (*FakeOTLP) Addr ¶
Addr returns the listener address (e.g. "127.0.0.1:42891") suitable for OTEL_EXPORTER_OTLP_ENDPOINT (prefix with http:// for plaintext or https:// for the TLS variant), typically set via t.Setenv before InitProvider.
func (*FakeOTLP) CertFile ¶
CertFile writes the receiver's self-signed certificate to a temp PEM file and returns its path, suitable for OTEL_EXPORTER_OTLP_CERTIFICATE. Only valid for a server constructed via NewFakeOTLPTLS.
func (*FakeOTLP) LastLogHeaders ¶
LastLogHeaders returns a copy of the gRPC metadata captured from the most recent log Export RPC, or nil if none.
func (*FakeOTLP) LastMetricHeaders ¶
LastMetricHeaders returns a copy of the gRPC metadata captured from the most recent metric Export RPC, or nil if none.
func (*FakeOTLP) LastTraceHeaders ¶
LastTraceHeaders returns a copy of the gRPC metadata captured from the most recent trace Export RPC, or nil if none.
func (*FakeOTLP) LogCount ¶
LogCount returns the total number of log records received across all RPCs.
func (*FakeOTLP) LogCountAtLevel ¶
LogCountAtLevel returns the count of log records whose severity number is >= minSeverity. OTel severity numbers: DEBUG=5, INFO=9, WARN=13, ERROR=17.
func (*FakeOTLP) MetricCount ¶
MetricCount returns the total number of metric records received, flattened across resource and scope groupings.
type MockCache ¶
type MockCache struct {
cache.Cache // Embed to satisfy remaining interface methods silently
InvNamespaces []cache.Namespace
InvErr error
// contains filtered or unexported fields
}
MockCache implements cache.Cache and records invalidated namespaces for testing.
func (*MockCache) GetNamespaces ¶
type MockConsumer ¶
type MockConsumer struct {
jetstream.Consumer
InfoVal *jetstream.ConsumerInfo
InfoErr error
}
MockConsumer embeds jetstream.Consumer so unused methods panic.
func (*MockConsumer) Info ¶
func (m *MockConsumer) Info(_ context.Context) (*jetstream.ConsumerInfo, error)
type MockDeduplicator ¶
type MockDeduplicator struct {
Err error // if set, CheckAndMark returns this error
// contains filtered or unexported fields
}
MockDeduplicator implements dedupe.Deduplicator for testing.
func NewMockDeduplicator ¶
func NewMockDeduplicator() *MockDeduplicator
func (*MockDeduplicator) CheckAndMark ¶
func (*MockDeduplicator) Close ¶
func (m *MockDeduplicator) Close() error
func (*MockDeduplicator) Stats ¶
func (m *MockDeduplicator) Stats() map[string]int64
type MockJetStream ¶
type MockJetStream struct {
jetstream.JetStream
StreamFn func(ctx context.Context, name string) (jetstream.Stream, error)
ConsumerFn func(ctx context.Context, stream, consumer string) (jetstream.Consumer, error)
PublishMsgFn func(ctx context.Context, msg *nats.Msg, opts ...jetstream.PublishOpt) (*jetstream.PubAck, error)
PubErr error // if set, PublishMsg returns this error (and skips recording)
// contains filtered or unexported fields
}
MockJetStream embeds jetstream.JetStream so unused methods panic. Override only what your test touches: StreamFn / ConsumerFn for sweeper-style code, PublishMsg recorder (Published + PubErr) for DLQ-style code.
func (*MockJetStream) PublishMsg ¶
func (m *MockJetStream) PublishMsg(ctx context.Context, msg *nats.Msg, opts ...jetstream.PublishOpt) (*jetstream.PubAck, error)
func (*MockJetStream) Published ¶
func (m *MockJetStream) Published() []*nats.Msg
Published returns a snapshot of PublishMsg calls recorded so far.
type MockJetStreamMsg ¶
type MockJetStreamMsg struct {
MsgData []byte
MsgSubject string
MsgHeaders nats.Header
// Configurable errors returned from the matching ack-family methods.
AckErr error
NakErr error
DoubleAckErr error
// State flags — atomic because production calls these from goroutines.
Acked atomic.Bool
Naked atomic.Bool
DoubleAcked atomic.Bool
}
MockJetStreamMsg implements jetstream.Msg for unit tests. Only the methods production code actually exercises (Data/Subject/Headers/Ack/Nak/DoubleAck) are overridden — calls to anything else panic, which is the loud-fail signal we want from a unit test mock.
func (*MockJetStreamMsg) Ack ¶
func (m *MockJetStreamMsg) Ack() error
func (*MockJetStreamMsg) Data ¶
func (m *MockJetStreamMsg) Data() []byte
func (*MockJetStreamMsg) Headers ¶
func (m *MockJetStreamMsg) Headers() nats.Header
func (*MockJetStreamMsg) InProgress ¶
func (m *MockJetStreamMsg) InProgress() error
func (*MockJetStreamMsg) Metadata ¶
func (m *MockJetStreamMsg) Metadata() (*jetstream.MsgMetadata, error)
func (*MockJetStreamMsg) Nak ¶
func (m *MockJetStreamMsg) Nak() error
func (*MockJetStreamMsg) NakWithDelay ¶
func (m *MockJetStreamMsg) NakWithDelay(_ time.Duration) error
The remaining jetstream.Msg methods are intentionally unimplemented — they panic to signal "the production code touched a path this test wasn't expecting." Implement on demand.
func (*MockJetStreamMsg) Reply ¶
func (m *MockJetStreamMsg) Reply() string
func (*MockJetStreamMsg) Subject ¶
func (m *MockJetStreamMsg) Subject() string
func (*MockJetStreamMsg) Term ¶
func (m *MockJetStreamMsg) Term() error
func (*MockJetStreamMsg) TermWithReason ¶
func (m *MockJetStreamMsg) TermWithReason(string) error
type MockPublisher ¶
type MockPublisher struct {
Messages []PublishedMessage
Err error // if set, Publish returns this error
// contains filtered or unexported fields
}
MockPublisher records all published messages for test assertions.
func (*MockPublisher) Close ¶
func (m *MockPublisher) Close() error
func (*MockPublisher) LastMessage ¶
func (m *MockPublisher) LastMessage() *PublishedMessage
LastMessage returns the most recently published message, or nil.
func (*MockPublisher) Publish ¶
func (m *MockPublisher) Publish(_ context.Context, subject string, data []byte, opts ...mq.PublishOpt) error
type MockRoundTripper ¶
type MockRoundTripper struct {
Fn func(req *http.Request) (*http.Response, error)
// contains filtered or unexported fields
}
MockRoundTripper intercepts HTTP requests for the components that take a custom *http.Client (today: the ClickHouse inserter). Fn lets a test hand-roll a response; if Fn is nil, returns 200 OK.
Every request is captured (with body slurped + replaced so the handler can still read it) — useful for asserting request shape after the fact.
func (*MockRoundTripper) Captured ¶
func (m *MockRoundTripper) Captured() []CapturedRequest
func (*MockRoundTripper) Hits ¶
func (m *MockRoundTripper) Hits() int32
type MockStream ¶
type MockStream struct {
jetstream.Stream
InfoVal *jetstream.StreamInfo
InfoErr error
Msgs map[uint64]*jetstream.RawStreamMsg
GetMsgFn func(ctx context.Context, seq uint64, opts ...jetstream.GetMsgOpt) (*jetstream.RawStreamMsg, error)
Purged []jetstream.StreamPurgeOpt
PurgeErr error
// contains filtered or unexported fields
}
MockStream embeds jetstream.Stream so unused methods panic. Provide Msgs for static GetMsg lookup, or GetMsgFn for richer behaviour. Purge calls are recorded into Purged.
func (*MockStream) GetMsg ¶
func (m *MockStream) GetMsg(ctx context.Context, seq uint64, opts ...jetstream.GetMsgOpt) (*jetstream.RawStreamMsg, error)
func (*MockStream) Info ¶
func (m *MockStream) Info(_ context.Context, _ ...jetstream.StreamInfoOpt) (*jetstream.StreamInfo, error)
func (*MockStream) Purge ¶
func (m *MockStream) Purge(_ context.Context, opts ...jetstream.StreamPurgeOpt) error
type MockSubscriber ¶
MockSubscriber implements mq.Subscriber for testing.
func (*MockSubscriber) Close ¶
func (m *MockSubscriber) Close() error
type PublishedMessage ¶
PublishedMessage records a single publish call.
type RoleCase ¶
type RoleCase struct {
Name string
AllowedRoles []string
Role string // role placed in context when SetRole is true
SetRole bool // false → no role in context at all (e.g. no token / invalid token)
// WantForbidden is true when the request must be rejected with 403.
WantForbidden bool
}
RoleCase is one row of a per-resource role-authorization matrix: a resource restricted to AllowedRoles, observed under a given request role.
func StandardRoleMatrix ¶
func StandardRoleMatrix() []RoleCase
StandardRoleMatrix is the canonical set of (AllowedRoles, observed-role) cases every AllowedRoles-style gate must cover. Authorization is allowlist membership: the observed role must appear in AllowedRoles, the admin role (policy.AdminRole — "admin" by default, which is what these cases assume) always passes, and an empty/absent role matches nothing — so a resource with no AllowedRoles authorizes nobody but admin. The empty/absent-role rows are the ones that fail open when a gate only enforces its allowlist for a non-empty role — the class of bug behind #159. Keeping them in one shared place means a handler that takes AllowedRoles without running this matrix looks obviously under-tested in review.