package web_test import ( "bytes" "context" "errors" "io" "log/slog" "net/http" "net/url" "sync" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "git.d-ma.be/mathias/tapir/internal/domain" ) // Error paths of the upload routes log a kind, never the error's text, which can carry // a file name, a path, a database value or a provider's output (adversarial review F5). type lockedBuf struct { mu sync.Mutex b bytes.Buffer } func (l *lockedBuf) Write(p []byte) (int, error) { l.mu.Lock() defer l.mu.Unlock() return l.b.Write(p) } func (l *lockedBuf) String() string { l.mu.Lock() defer l.mu.Unlock() return l.b.String() } type failingUploader struct{ err error } func (f failingUploader) Accept(context.Context, string, string, domain.Language, io.Reader) (string, error) { return "", f.err } func TestUploadPost_AnUnexpectedFailureLogsAKindAndNotItsText(t *testing.T) { app, _ := newUploadApp(t, audioOK, 200<<20) buf := &lockedBuf{} app.Log = slog.New(slog.NewTextHandler(buf, nil)) app.Uploads = failingUploader{err: errors.New(`ffprobe: Error opening input file /spool/upload-555 for "zebrakaka-meeting.m4a"`)} rec := do(t, app, uploadReq(t, "t", "zebrakaka-meeting.m4a", []byte("x"))) assert.Equal(t, http.StatusInternalServerError, rec.Code) out := buf.String() assert.NotContains(t, out, "zebrakaka", "a file name is private content") assert.NotContains(t, out, "/spool/", "a spool path carries the upload's identity") assert.Contains(t, out, "kind=error") } func TestUploadChatPost_AModelFailureLogsAKindAndNotItsText(t *testing.T) { app, fc := newUploadChatApp(t) buf := &lockedBuf{} app.Log = slog.New(slog.NewTextHandler(buf, nil)) fc.err = errors.New("LLM returned 400: the prompt was zebrakaka secret words") id := seedUpload(t, userID, "m", "done", meetingText) rec := do(t, app, chatPost("/uploads/"+id+"/chat", url.Values{"question": {"q"}})) require.Equal(t, http.StatusOK, rec.Code) out := buf.String() assert.NotContains(t, out, "zebrakaka") assert.Contains(t, out, "kind=error") }