testutil

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 13, 2026 License: Apache-2.0 Imports: 42 Imported by: 0

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

View Source
const TestJWTSecret = "test-secret-at-least-32-chars-long!"

TestJWTSecret is a shared HMAC secret for test JWT generation.

View Source
const TestServerVersion = "24.8.1.1"

TestServerVersion is the ClickHouse version NewTestSchemaRegistry's mock connection reports, so a test can assert against ServerVersion() without hardcoding the same literal twice.

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

func MakeExpiredJWT(t *testing.T, claims map[string]any) string

MakeExpiredJWT creates a signed HS256 JWT that expired 1 hour ago.

func MakeJWT

func MakeJWT(t *testing.T, claims map[string]any) string

MakeJWT creates a signed HS256 JWT with the given claims merged with standard exp/iat.

func NewEmbeddedMQ added in v0.2.0

func NewEmbeddedMQ(t testing.TB, maxBytes int64, tenants ...tenant.ID) *mq.EmbeddedNATS

NewEmbeddedMQ starts the embedded broker over a storedir.New directory, closed by the test framework, with a queue open for each of tenants — tenant.Default when none is named — at maxBytes: a tenant has a queue once its budget is applied, as the wiring does for every tenant it serves.

func NewSchemaConn added in v0.2.0

func NewSchemaConn(tables []*discovery.TableSchema) driver.Conn

NewSchemaConn is the mock connection NewTestSchemaRegistry discovers from, for a test that builds the registry itself.

func NewTestSchemaRegistry

func NewTestSchemaRegistry(t testing.TB, tables []*discovery.TableSchema) *discovery.SchemaRegistry

NewTestSchemaRegistry creates a SchemaRegistry pre-loaded with the given table schemas, without a real ClickHouse: a mock connection serves the schemas as system.columns rows (UTC as the server zone) and the registry is built by the real discovery path — NewSchemaRegistry + Refresh — so timestamp column specs are precomputed exactly as in production.

The registry holds schemas rebuilt from those rows (Name, Type, HasDefault, DefaultKind, DefaultExpression, Position, DDL; IsNullable derived from the type string), not the caller's structs.

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

type CapturedRequest struct {
	Method string
	URL    string
	Header http.Header
	Body   []byte
}

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

func NewFakeOTLP(t *testing.T) *FakeOTLP

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

func NewFakeOTLPTLS(t *testing.T) *FakeOTLP

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

func (r *FakeOTLP) Addr() string

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

func (r *FakeOTLP) CertFile(t *testing.T) string

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

func (r *FakeOTLP) LastLogHeaders() metadata.MD

LastLogHeaders returns a copy of the gRPC metadata captured from the most recent log Export RPC, or nil if none.

func (*FakeOTLP) LastMetricHeaders

func (r *FakeOTLP) LastMetricHeaders() metadata.MD

LastMetricHeaders returns a copy of the gRPC metadata captured from the most recent metric Export RPC, or nil if none.

func (*FakeOTLP) LastTraceHeaders

func (r *FakeOTLP) LastTraceHeaders() metadata.MD

LastTraceHeaders returns a copy of the gRPC metadata captured from the most recent trace Export RPC, or nil if none.

func (*FakeOTLP) LogCount

func (r *FakeOTLP) LogCount() int

LogCount returns the total number of log records received across all RPCs.

func (*FakeOTLP) LogCountAtLevel

func (r *FakeOTLP) LogCountAtLevel(minSeverity int32) int

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

func (r *FakeOTLP) MetricCount() int

MetricCount returns the total number of metric records received, flattened across resource and scope groupings.

func (*FakeOTLP) Reset

func (r *FakeOTLP) Reset()

Reset clears all captured payloads. Useful between test phases.

func (*FakeOTLP) SpanCount

func (r *FakeOTLP) SpanCount() int

SpanCount returns the total number of spans received across all RPCs. Spans are flattened across resource and scope groupings.

type MockCache

type MockCache struct {
	cache.Cache   // Embed to satisfy remaining interface methods silently
	InvNamespaces []cache.Namespace
	// InvTenants records every InvalidateTenant, in order.
	InvTenants []tenant.ID
	InvErr     error
	// contains filtered or unexported fields
}

MockCache implements cache.Cache and records invalidated namespaces for testing.

func (*MockCache) GetNamespaces

func (m *MockCache) GetNamespaces() []cache.Namespace

func (*MockCache) GetTenants added in v0.2.0

func (m *MockCache) GetTenants() []tenant.ID

GetTenants returns the tenants InvalidateTenant was called for, in order.

func (*MockCache) Invalidate

func (m *MockCache) Invalidate(_ context.Context, namespaces []cache.Namespace) (uint64, error)

func (*MockCache) InvalidateTenant added in v0.2.0

func (m *MockCache) InvalidateTenant(_ context.Context, id tenant.ID) error

type MockDeadLetterStats added in v0.2.0

type MockDeadLetterStats struct {
	Counts mq.DeadLetterCounts
	Err    error

	Tenant tenant.ID
	Table  string
	// contains filtered or unexported fields
}

MockDeadLetterStats implements mq.DeadLetterStats with a canned answer, recording the tenant and table each call asked about.

func (*MockDeadLetterStats) DeadLetterCounts added in v0.2.0

func (m *MockDeadLetterStats) DeadLetterCounts(_ context.Context, id tenant.ID, table string) (mq.DeadLetterCounts, error)

type MockDeduplicator

type MockDeduplicator struct {

	// Err, if set, fails Reserve — after ErrAfter calls have succeeded;
	// CommitErr and ReleaseErr fail their phase.
	Err        error
	ErrAfter   int
	CommitErr  error
	ReleaseErr error
	Released   []dedupe.Claim // every claim Release was given
	// Calls to each phase, for tests that count round trips.
	Reserves, Commits int
	// contains filtered or unexported fields
}

MockDeduplicator implements dedupe.Deduplicator in memory, with per-phase error injection and a record of what was committed and released.

func NewMockDeduplicator

func NewMockDeduplicator() *MockDeduplicator

func (*MockDeduplicator) Close

func (m *MockDeduplicator) Close() error

func (*MockDeduplicator) Commit added in v0.2.0

func (m *MockDeduplicator) Commit(_ context.Context, claims []dedupe.Claim, retention time.Duration) error

func (*MockDeduplicator) Committed added in v0.2.0

func (m *MockDeduplicator) Committed(k dedupe.Key) bool

Committed reports whether k was committed.

func (*MockDeduplicator) Hold added in v0.2.0

func (m *MockDeduplicator) Hold(k dedupe.Key)

Hold claims k as another in-flight request would, so Reserve answers InFlight for it.

func (*MockDeduplicator) Pending added in v0.2.0

func (m *MockDeduplicator) Pending(k dedupe.Key) bool

Pending reports whether k is claimed and neither committed nor released.

func (*MockDeduplicator) Release added in v0.2.0

func (m *MockDeduplicator) Release(_ context.Context, claims []dedupe.Claim) error

func (*MockDeduplicator) Reserve added in v0.2.0

func (m *MockDeduplicator) Reserve(_ context.Context, keys []dedupe.Key, _ time.Duration) ([]dedupe.Claim, error)

Reserve answers Duplicate for a key repeated in one call, as Managed does.

func (*MockDeduplicator) Retention added in v0.2.0

func (m *MockDeduplicator) Retention(k dedupe.Key) time.Duration

Retention is the retention k was last committed with.

type MockMessage added in v0.2.0

type MockMessage struct {
	MsgData  []byte
	MsgTopic mq.Topic

	// 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
	// NakDelay is the delay of the last NakWithDelay (which also sets Naked).
	NakDelay atomic.Int64
}

MockMessage builds mq.Messages whose ack-family callbacks flip the flags below, so a test can assert what the worker did with a message.

func (*MockMessage) Message added in v0.2.0

func (m *MockMessage) Message() *mq.Message

Message returns an mq.Message wired to this mock's flags. Every call returns a new Message sharing the same flags.

type MockPublisher

type MockPublisher struct {
	Messages []PublishedMessage
	Err      error // if set, Publish and DeadLetter return this error
	// ErrAfter lets that many calls succeed before Err applies, to fail a
	// batch part-way through.
	ErrAfter int
	// contains filtered or unexported fields
}

MockPublisher records all published messages — ingest publishes and dead-letter parkings alike — for test assertions.

func (*MockPublisher) Close

func (m *MockPublisher) Close() error

func (*MockPublisher) DeadLetter added in v0.2.0

func (m *MockPublisher) DeadLetter(_ context.Context, msg *mq.Message, opts ...mq.PublishOpt) 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, topic mq.Topic, data []byte, opts ...mq.PublishOpt) error

func (*MockPublisher) Published added in v0.2.0

func (m *MockPublisher) Published() []PublishedMessage

Published returns a snapshot of the publish calls recorded so far.

type MockPurger added in v0.2.0

type MockPurger struct {
	Purged bool  // what PurgeAcked reports
	Err    error // if set, PurgeAcked returns this error

	Calls []PurgeCall
	// contains filtered or unexported fields
}

MockPurger implements mq.Purger and records every call.

func (*MockPurger) PurgeAcked added in v0.2.0

func (m *MockPurger) PurgeAcked(_ context.Context, consumer string, olderThan map[tenant.ID]time.Time) (bool, 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

func (*MockRoundTripper) RoundTrip

func (m *MockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)

type MockSubscriber

type MockSubscriber struct {
	Err     error
	Handler func(msg *mq.Message) error
}

MockSubscriber implements mq.Subscriber for testing.

func (*MockSubscriber) Close

func (m *MockSubscriber) Close() error

func (*MockSubscriber) Subscribe

func (m *MockSubscriber) Subscribe(_ context.Context, _ string, handler func(msg *mq.Message) error) error

type PublishedMessage

type PublishedMessage struct {
	Topic      mq.Topic
	DeadLetter bool // parked via DeadLetter rather than published via Publish
	Data       []byte
	Headers    mq.Headers
}

PublishedMessage records a single Publish or DeadLetter call, with the headers the options set.

type PurgeCall added in v0.2.0

type PurgeCall struct {
	Consumer  string
	OlderThan map[tenant.ID]time.Time
}

PurgeCall records one PurgeAcked 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.

type UTCRow

type UTCRow struct{ driver.Row }

UTCRow is a driver.Row stub answering Refresh's single-string probes with "UTC" — for any mock driver.Conn that must satisfy the schema-refresh path without caring which probe it is answering.

func (UTCRow) Scan

func (UTCRow) Scan(dest ...any) error

Directories

Path Synopsis
Package cachetest is the conformance suite every cache.Cache backend runs: what a hit, a miss and an invalidation mean, independent of where the entries and versions live.
Package cachetest is the conformance suite every cache.Cache backend runs: what a hit, a miss and an invalidation mean, independent of where the entries and versions live.
Package logtest points slog's default logger somewhere a test can use.
Package logtest points slog's default logger somewhere a test can use.
Package mutationtest holds the statements api.IsMutation is tested on, shared by its unit test and by the integration test that checks each one against ClickHouse's own parser.
Package mutationtest holds the statements api.IsMutation is tested on, shared by its unit test and by the integration test that checks each one against ClickHouse's own parser.
Package storedir gives a test a directory for the embedded message broker's store.
Package storedir gives a test a directory for the embedded message broker's store.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL