package testutil import ( "bytes" "context" "net/http" "net/http/httptest" "sync" "testing" "time" "galaxy/gateway/internal/logging" "galaxy/gateway/internal/telemetry" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // LogBuffer is a concurrency-safe in-memory buffer used by observability // tests. type LogBuffer struct { mu sync.Mutex buf bytes.Buffer } // Write appends p to the buffer. func (b *LogBuffer) Write(p []byte) (int, error) { b.mu.Lock() defer b.mu.Unlock() return b.buf.Write(p) } // String returns the current buffer contents. func (b *LogBuffer) String() string { b.mu.Lock() defer b.mu.Unlock() return b.buf.String() } // NewObservedLogger constructs a JSON zap logger that writes into an in-memory // buffer suitable for log assertions. func NewObservedLogger(t *testing.T) (*zap.Logger, *LogBuffer) { t.Helper() buffer := &LogBuffer{} encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.TimeKey = "timestamp" encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder core := zapcore.NewCore( zapcore.NewJSONEncoder(encoderConfig), zapcore.Lock(zapcore.AddSync(buffer)), zap.DebugLevel, ) logger := zap.New(core) t.Cleanup(func() { require.NoError(t, logging.Sync(logger)) }) return logger, buffer } // NewTelemetryRuntime constructs a telemetry runtime for tests and shuts it // down automatically. func NewTelemetryRuntime(t *testing.T, logger *zap.Logger) *telemetry.Runtime { t.Helper() runtime, err := telemetry.New(context.Background(), logger) require.NoError(t, err) t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() require.NoError(t, runtime.Shutdown(ctx)) }) return runtime } // ScrapeMetrics returns the Prometheus exposition produced by handler. func ScrapeMetrics(t *testing.T, handler http.Handler) string { t.Helper() req := httptest.NewRequest(http.MethodGet, "/metrics", nil) recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, req) require.Equal(t, http.StatusOK, recorder.Code) return recorder.Body.String() }