package server import ( "bytes" "log/slog" "net/http" "net/http/httptest" "strings" "testing" "chat-agent-system/internal/config" ) func TestAccessLogPath_DropsQueryAndFragment(t *testing.T) { got := accessLogPath("/api/v1/chat?token=secret&q=1#frag") if got != "/api/v1/chat" { t.Fatalf("path=%q", got) } msg := accessLogSafeMessage("GET", "/x?password=p", 200, 3, "req_1", "") blob := strings.ToLower(strings.Join([]string{ msg["method"].(string), msg["path"].(string), msg["request_id"].(string), }, " ")) for _, leak := range []string{"password", "token", "secret", "bearer", "authorization"} { if strings.Contains(blob, leak) { t.Fatalf("leak %q in %#v", leak, msg) } } } func TestAccessLogMiddleware_EmitsSafeFieldsOnly(t *testing.T) { var buf bytes.Buffer prev := slog.Default() slog.SetDefault(slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}))) t.Cleanup(func() { slog.SetDefault(prev) }) srv := &Server{cfg: &config.Config{HTTPAccessLogEnabled: true}} inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Authorization", "Bearer should-not-log") w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(`{"password":"nope","prompt":"secret text"}`)) }) handler := srv.accessLogMiddleware(inner) req := httptest.NewRequest(http.MethodPost, "/api/v1/login?access_token=leak", strings.NewReader(`{"password":"x"}`)) req.Header.Set("Authorization", "Bearer super-secret") req.Header.Set("Cookie", "session=abc") req.Header.Set("X-Request-ID", "req_test_access") rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusCreated { t.Fatalf("status=%d", rec.Code) } line := buf.String() if !strings.Contains(line, `"msg":"http_access"`) && !strings.Contains(line, `"http_access"`) { t.Fatalf("missing http_access log: %s", line) } if !strings.Contains(line, `"path":"/api/v1/login"`) { t.Fatalf("expected redacted path, got %s", line) } if !strings.Contains(line, `"status":201`) { t.Fatalf("expected status, got %s", line) } if !strings.Contains(line, "req_test_access") { t.Fatalf("expected request_id, got %s", line) } low := strings.ToLower(line) for _, leak := range []string{"super-secret", "access_token=leak", "password", "bearer super", "session=abc", "secret text"} { if strings.Contains(low, leak) { t.Fatalf("log leaked %q: %s", leak, line) } } } func TestAccessLogMiddleware_DisabledIsNoOp(t *testing.T) { var buf bytes.Buffer prev := slog.Default() slog.SetDefault(slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}))) t.Cleanup(func() { slog.SetDefault(prev) }) srv := &Server{cfg: &config.Config{HTTPAccessLogEnabled: false}} handler := srv.accessLogMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) })) handler.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/health", nil)) if strings.Contains(buf.String(), "http_access") { t.Fatalf("disabled middleware must not log: %s", buf.String()) } }