ultisuite-backend/internal/api/middleware/logging_test.go
R3D347HR4Y 2057ccd816 Add observability features with Prometheus and Grafana integration
- Introduced health checks for Nextcloud, Immich, and Jitsi in the .env.example file.
- Implemented Prometheus metrics for HTTP requests, IMAP sync, outbox processing, and webhook executions.
- Added Grafana configuration files for dashboards and data sources.
- Updated Docker Compose to include Prometheus and Grafana services.
- Enhanced logging middleware to include request IDs and metrics tracking.
- Created health checker for monitoring database and external service statuses.
- Updated README with observability setup instructions and service URLs.
2026-05-22 16:17:10 +02:00

109 lines
2.9 KiB
Go

package middleware
import (
"bytes"
"encoding/json"
"log/slog"
"net/http"
"net/http/httptest"
"testing"
"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
)
func withTestLogger(t *testing.T) *bytes.Buffer {
t.Helper()
var buf bytes.Buffer
old := slog.Default()
slog.SetDefault(slog.New(slog.NewJSONHandler(&buf, nil)))
t.Cleanup(func() { slog.SetDefault(old) })
return &buf
}
func parseLogRecord(t *testing.T, buf *bytes.Buffer) map[string]any {
t.Helper()
var record map[string]any
if err := json.Unmarshal(buf.Bytes(), &record); err != nil {
t.Fatalf("unmarshal log record: %v", err)
}
return record
}
func TestLoggingIncludesRequestFields(t *testing.T) {
buf := withTestLogger(t)
handler := Logging(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
}))
req := httptest.NewRequest(http.MethodPost, "/api/messages", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
record := parseLogRecord(t, buf)
if record["msg"] != "request" {
t.Fatalf("msg = %v, want request", record["msg"])
}
if record["method"] != http.MethodPost {
t.Fatalf("method = %v, want POST", record["method"])
}
if record["path"] != "/api/messages" {
t.Fatalf("path = %v, want /api/messages", record["path"])
}
if record["status"] != float64(http.StatusCreated) {
t.Fatalf("status = %v, want %d", record["status"], http.StatusCreated)
}
if _, ok := record["duration"]; !ok {
t.Fatal("expected duration field in log record")
}
}
func TestLoggingIncludesRequestIDFromContext(t *testing.T) {
buf := withTestLogger(t)
handler := Logging(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
req := httptest.NewRequest(http.MethodGet, "/", nil)
req = req.WithContext(apiresponse.WithTraceID(req.Context(), "trace-abc"))
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
record := parseLogRecord(t, buf)
if record["request_id"] != "trace-abc" {
t.Fatalf("request_id = %v, want trace-abc", record["request_id"])
}
}
func TestLoggingOmitsRequestIDWhenMissing(t *testing.T) {
buf := withTestLogger(t)
handler := Logging(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
record := parseLogRecord(t, buf)
if _, ok := record["request_id"]; ok {
t.Fatalf("request_id = %v, want field omitted", record["request_id"])
}
}
func TestLoggingWithTraceMiddlewareUsesClientTraceID(t *testing.T) {
buf := withTestLogger(t)
handler := TraceID(Logging(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})))
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
req.Header.Set(apiresponse.TraceIDHeader, "client-trace")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
record := parseLogRecord(t, buf)
if record["request_id"] != "client-trace" {
t.Fatalf("request_id = %v, want client-trace", record["request_id"])
}
}