Skip to content
Open
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
24 changes: 24 additions & 0 deletions internal/observability/cleanup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package observability

import (
"context"
"testing"
"time"
)

func TestWaitForCleanup(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()

done := make(chan struct{})
go func() {
defer close(done)
WaitForCleanup(ctx)
}()

select {
case <-done:
case <-time.After(time.Second):
t.Fatal("WaitForCleanup did not return after context cancellation")
}
}
56 changes: 56 additions & 0 deletions internal/observability/logging_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package observability

import (
"os"
"path/filepath"
"sync"
"testing"

"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"github.com/supabase/auth/internal/conf"
)

func TestConfigureLoggingWithFile(t *testing.T) {
// loggingOnce must be reset for this branch to run; we can only do this
// safely from inside the package's own tests.
loggingOnce = sync.Once{}

dir := t.TempDir()
logFile := filepath.Join(dir, "test.log")

require.NoError(t, ConfigureLogging(&conf.LoggingConfig{
File: logFile,
Level: "info",
Fields: map[string]interface{}{
"env": "test",
"region": "us-east-1",
},
SQL: LOG_SQL_ALL,
}))

// The configure path opens the log file before writing the "Set output
// file to ..." entry; the file must exist after the call returns.
_, err := os.Stat(logFile)
require.NoError(t, err)
}

func TestNewCustomFormatterFormatsTimeAsUTC(t *testing.T) {
f := NewCustomFormatter()
require.NotNil(t, f)

entry := logrus.NewEntry(logrus.New())
entry.Message = "hello"
out, err := f.Format(entry)
require.NoError(t, err)
require.Contains(t, string(out), "hello")
}

func TestSetPopLoggerExecutesAllSQLConfigs(t *testing.T) {
// setPopLogger registers a closure with pop.SetLogger; calling it for
// each documented SQL log mode covers the three branches that select
// shouldLogSQL and shouldLogSQLArgs.
for _, mode := range []string{LOG_SQL_NONE, LOG_SQL_STATEMENT, LOG_SQL_ALL, ""} {
setPopLogger(mode)
}
}
68 changes: 68 additions & 0 deletions internal/observability/metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package observability

import (
"context"
"sync"
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/supabase/auth/internal/conf"
)

func TestMeter(t *testing.T) {
require.NotNil(t, Meter("test-meter"))
}

func TestObtainMetricCounter(t *testing.T) {
require.NotNil(t, ObtainMetricCounter("test_counter", "a counter for tests"))
}

func TestConfigureMetricsNilContextPanics(t *testing.T) {
require.PanicsWithValue(t, "context must not be nil", func() {
_ = ConfigureMetrics(nil, &conf.MetricsConfig{})
})
}

func TestConfigureMetricsDisabled(t *testing.T) {
// metricsOnce is a package-level *sync.Once used to ensure ConfigureMetrics
// runs exactly once per process. Reset it for this isolated test so we can
// exercise the disabled-config branch without coupling to other tests.
metricsOnce = &sync.Once{}
require.NoError(t, ConfigureMetrics(context.Background(), &conf.MetricsConfig{Enabled: false}))
}

func TestEnableOpenTelemetryMetricsRejectsUnsupportedExporter(t *testing.T) {
err := enableOpenTelemetryMetrics(context.Background(), &conf.MetricsConfig{ExporterProtocol: "http/json"})
require.Error(t, err)
require.Contains(t, err.Error(), "unsupported OpenTelemetry exporter protocol")
}

func TestEnablePrometheusMetricsStartsAndShutsDown(t *testing.T) {
// Coverage of the synchronous setup path; the goroutine that calls
// ListenAndServe shuts down when the context is cancelled. The function
// returns nil whether or not the listener binds, so we only assert the
// happy synchronous outcome.
ctx, cancel := context.WithCancel(context.Background())
port := freeLocalPort(t)
require.NoError(t, enablePrometheusMetrics(ctx, &conf.MetricsConfig{
PrometheusListenHost: "127.0.0.1",
PrometheusListenPort: port,
}))
cancel()
time.Sleep(150 * time.Millisecond)
}

func TestEnableOpenTelemetryMetricsGRPC(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
require.NoError(t, enableOpenTelemetryMetrics(ctx, &conf.MetricsConfig{ExporterProtocol: "grpc"}))
cancel()
time.Sleep(150 * time.Millisecond)
}

func TestEnableOpenTelemetryMetricsHTTPProtobuf(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
require.NoError(t, enableOpenTelemetryMetrics(ctx, &conf.MetricsConfig{ExporterProtocol: "http/protobuf"}))
cancel()
time.Sleep(150 * time.Millisecond)
}
78 changes: 78 additions & 0 deletions internal/observability/profiler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package observability

import (
"context"
"net"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/supabase/auth/internal/conf"
)

// freeLocalPort returns the string form of a free TCP port on 127.0.0.1.
// Used by tests that need to start a real listener (profiler, prometheus)
// without colliding with other services or other tests running in parallel.
func freeLocalPort(t *testing.T) string {
t.Helper()
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
port := l.Addr().(*net.TCPAddr).Port
require.NoError(t, l.Close())
return strconv.Itoa(port)
}

func TestConfigureProfilerDisabled(t *testing.T) {
require.NoError(t, ConfigureProfiler(context.Background(), &conf.ProfilerConfig{Enabled: false}))
}

func TestConfigureProfilerStartsAndShutsDown(t *testing.T) {
// Start the profiler on a free localhost port and immediately cancel the
// context; the cleanup goroutine should run server.Shutdown without
// hanging the test. The function returns nil regardless of whether the
// underlying listener bound successfully, so coverage focuses on the
// synchronous setup path.
ctx, cancel := context.WithCancel(context.Background())
port := freeLocalPort(t)
require.NoError(t, ConfigureProfiler(ctx, &conf.ProfilerConfig{
Enabled: true,
Host: "127.0.0.1",
Port: port,
}))
cancel()
// Give the cleanup goroutine a brief moment to run.
time.Sleep(150 * time.Millisecond)
}

func TestProfilerHandlerServeHTTP(t *testing.T) {
tests := []struct {
name string
path string
expectedStatus int
}{
{name: "pprof index", path: "/debug/pprof/", expectedStatus: http.StatusOK},
{name: "pprof cmdline", path: "/debug/pprof/cmdline", expectedStatus: http.StatusOK},
{name: "pprof symbol", path: "/debug/pprof/symbol", expectedStatus: http.StatusOK},
{name: "pprof goroutine", path: "/debug/pprof/goroutine", expectedStatus: http.StatusOK},
{name: "pprof heap", path: "/debug/pprof/heap", expectedStatus: http.StatusOK},
{name: "pprof allocs", path: "/debug/pprof/allocs", expectedStatus: http.StatusOK},
{name: "pprof threadcreate", path: "/debug/pprof/threadcreate", expectedStatus: http.StatusOK},
{name: "pprof block", path: "/debug/pprof/block", expectedStatus: http.StatusOK},
{name: "pprof mutex", path: "/debug/pprof/mutex", expectedStatus: http.StatusOK},
{name: "unknown path returns 404", path: "/debug/pprof/unknown", expectedStatus: http.StatusNotFound},
{name: "non-pprof path returns 404", path: "/something/else", expectedStatus: http.StatusNotFound},
}

handler := &ProfilerHandler{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, tt.path, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
require.Equal(t, tt.expectedStatus, w.Code)
})
}
}
74 changes: 74 additions & 0 deletions internal/observability/request-logger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,77 @@ func TestContext(t *testing.T) {
}
}
}

func TestNewLogEntry(t *testing.T) {
le := NewLogEntry(logrus.NewEntry(logrus.StandardLogger()))
require.NotNil(t, le)
// NewLogEntry returns a chimiddleware.LogEntry; verify the underlying type
// is the package's *logEntry so downstream casts in GetLogEntry work.
_, ok := le.(*logEntry)
require.True(t, ok, "expected NewLogEntry to return *logEntry")
}

func TestGetLogEntryFallback(t *testing.T) {
// No log entry is attached to this request's context, so GetLogEntry
// should return a fresh fallback entry rather than panic or nil.
req := httptest.NewRequest(http.MethodGet, "/", nil)
got := GetLogEntry(req)
require.NotNil(t, got)
require.NotNil(t, got.Entry)
}

func TestGetLogEntryReturnsAttachedEntry(t *testing.T) {
want := &logEntry{Entry: logrus.NewEntry(logrus.StandardLogger())}
req := httptest.NewRequest(http.MethodGet, "/", nil)
ctx := SetLogEntryWithContext(req.Context(), want)
req = req.WithContext(ctx)

got := GetLogEntry(req)
require.Same(t, want, got)
}

func TestLogEntrySetField(t *testing.T) {
le := &logEntry{Entry: logrus.NewEntry(logrus.StandardLogger())}
req := httptest.NewRequest(http.MethodGet, "/", nil)
req = req.WithContext(SetLogEntryWithContext(req.Context(), le))

LogEntrySetField(req, "user_id", "abc-123")
require.Equal(t, "abc-123", le.Entry.Data["user_id"])
}

func TestLogEntrySetFieldsMerges(t *testing.T) {
le := &logEntry{Entry: logrus.NewEntry(logrus.StandardLogger())}
req := httptest.NewRequest(http.MethodGet, "/", nil)
req = req.WithContext(SetLogEntryWithContext(req.Context(), le))

LogEntrySetFields(req, logrus.Fields{
"session": "s1",
"trace": "t1",
})
require.Equal(t, "s1", le.Entry.Data["session"])
require.Equal(t, "t1", le.Entry.Data["trace"])
}

func TestLogEntrySetFieldNoLogEntryInContextIsNoop(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
require.NotPanics(t, func() {
LogEntrySetField(req, "k", "v")
LogEntrySetFields(req, logrus.Fields{"k": "v"})
})
}

func TestLogEntryPanicWritesPanicAndStackFields(t *testing.T) {
var buf bytes.Buffer
logger := logrus.New()
logger.SetOutput(&buf)
logger.SetFormatter(&logrus.JSONFormatter{})

le := &logEntry{Entry: logrus.NewEntry(logger)}
le.Panic("boom", []byte("fake-stack-trace"))

var out map[string]interface{}
require.NoError(t, json.Unmarshal(buf.Bytes(), &out))
require.Equal(t, "request panicked", out["msg"])
require.Equal(t, "fake-stack-trace", out["stack"])
require.Contains(t, out["panic"], "boom")
}
Loading