package logging
import (
"bytes"
"errors"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestFreeTextAuthorizationAndUnlabelledPrivatePEMAreHidden(t *testing.T) {
for _, debug := range []bool{false, true} {
out := installTestLogger(t, debug)
cause := "upstream denied\nAuthorization: Basic dXNlcjpwYXNzd29yZA==\nProxy-Authorization: Digest username=private-user, response=private-response\n-----BEGIN RSA PRIVATE KEY-----\nprivate-pem-material\n-----END RSA PRIVATE KEY-----\nretry attempt failed"
slog.Error("workflow failed", "error", errors.New(cause))
for _, secret := range []string{"dXNlcjpwYXNzd29yZA==", "private-user", "private-response", "private-pem-material"} {
if strings.Contains(out.String(), secret) {
t.Fatalf("leak %s: %s", secret, out.String())
}
}
for _, keep := range []string{"upstream denied", "retry attempt failed"} {
if !strings.Contains(out.String(), keep) {
t.Fatal("failure reason lost", out.String())
}
}
}
}
func TestJSONErrorAuthorizationDoesNotHideBusinessFields(t *testing.T) {
out := installTestLogger(t, false)
slog.Error("request failed", "error", errors.New(`denied: {"Authorization":"Basic dXNlcjpwYXNzd29yZA==","task_id":"4","reason":"quota full"}`))
text := out.String()
if strings.Contains(text, "dXNlcjpwYXNzd29yZA==") || !strings.Contains(text, "quota full") || !strings.Contains(text, "task_id") {
t.Fatal(text)
}
}
func TestReadableMarkupFormAndMultilineConversationAreSanitized(t *testing.T) {
cases := []struct {
body string
keep string
secrets []string
}{
{`readyprivate-markup-keyprivate-markup-prompt27`, "27", []string{"private-markup-key", "private-markup-prompt", "private-input-password"}},
{"status=ready&api_key=private-form-key&remaining=27", "remaining=27", []string{"private-form-key"}},
{"status=ready\nprompt:\n private-multiline-prompt\n still-private\n\nremaining=27", "remaining=27", []string{"private-multiline-prompt", "still-private"}},
}
for _, tc := range cases {
safe, err := RedactText([]byte(tc.body))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(safe, tc.keep) {
t.Fatal("business content lost", safe)
}
for _, s := range tc.secrets {
if strings.Contains(safe, s) {
t.Fatalf("leaked %s: %s", s, safe)
}
}
}
}
func TestReadablePlainHTTPBodiesAreObservedSafelyWithoutChangingBytes(t *testing.T) {
body := "status=ready\ntask_id=4\nremaining=27\nAuthorization: Basic dXNlcjpwYXNzd29yZA==\napi_key=private-key\n-----BEGIN PRIVATE KEY-----\nprivate-pem-material\n-----END PRIVATE KEY-----\nretry_after=60\n" + strings.Repeat("business-data ", 12000)
for _, debug := range []bool{false, true} {
out := installTestLogger(t, debug)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
if string(b) != body {
t.Error("request changed")
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
io.WriteString(w, body)
}))
req, _ := http.NewRequest(http.MethodPost, server.URL, strings.NewReader(body))
req.Header.Set("Content-Type", "text/plain")
res, err := HTTPClient(server.Client(), "dispatcher", "saas").Do(req)
if err != nil {
t.Fatal(err)
}
got, err := io.ReadAll(res.Body)
res.Body.Close()
server.Close()
if err != nil || !bytes.Equal(got, []byte(body)) {
t.Fatal("response changed", err)
}
text := out.String()
for _, secret := range []string{"dXNlcjpwYXNzd29yZA==", "private-key", "private-pem-material"} {
if strings.Contains(text, secret) {
t.Fatalf("leak %s", secret)
}
}
for _, keep := range []string{"remaining=27", "retry_after=60", "business-data"} {
if strings.Contains(text, keep) != debug {
t.Fatalf("body absent/truncated or exposed in normal mode: %v %s", debug, keep)
}
}
if debug && !strings.Contains(text, `"body_encoding":"utf-8"`) {
t.Fatal("text not identified", text)
}
}
}