package web_test import ( "bytes" "context" "io" "log/slog" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Input that cannot be right is the caller's to fix: a 4xx, never a 500 (adversarial // review F7). No cross-tenant oracle either way: unknown and foreign ids look the same. func TestUploadRoutes_ANonUUIDIdIs404NotA500(t *testing.T) { app, _ := newUploadApp(t, audioOK, 200<<20) for _, path := range []string{"/uploads/not-a-uuid", "/uploads/not-a-uuid/status", "/uploads/not-a-uuid/chat", "/uploads/%00/status", "/uploads/1/status"} { t.Run(path, func(t *testing.T) { rec := do(t, app, get(path)) assert.Equal(t, http.StatusNotFound, rec.Code) }) } } func TestUploadPost_ATitleWithControlCharactersOrInvalidUTF8IsCleanedNotA500(t *testing.T) { tests := []struct{ name, title, filename string }{ {"a NUL in the title", "Sprint\x00review", "a.m4a"}, {"invalid UTF-8 in the title", "Sprint \xff\xfe review", "a.m4a"}, {"a newline in the title", "Sprint\nreview", "a.m4a"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { app, sub := newUploadApp(t, audioOK, 200<<20) rec := do(t, app, uploadReq(t, tt.title, tt.filename, []byte("synthetic audio bytes"))) require.Equal(t, http.StatusSeeOther, rec.Code, "bad bytes in a cosmetic title must not fail the upload: %s", body(t, rec)) require.Len(t, sub.jobs, 1) var title string require.NoError(t, rawPool(t).QueryRow(context.Background(), `SELECT title FROM uploaded_transcripts WHERE id = $1`, sub.jobs[0].UploadID).Scan(&title)) assert.NotContains(t, title, "\x00") assert.NotContains(t, title, "\n") assert.True(t, len(title) > 0, "a title is still shown") }) } } func TestUploadPost_AMalformedMultipartHeaderIsA400AndItsTextIsNotLogged(t *testing.T) { // A control byte in the part's file name makes the multipart body malformed. It is the // sender's to fix, and the parser's error quotes the header, which carries the name. app, sub := newUploadApp(t, audioOK, 200<<20) buf := &lockedBuf{} app.Log = slog.New(slog.NewTextHandler(buf, nil)) rec := do(t, app, uploadReq(t, "", "zebrakaka\x00weekly.m4a", []byte("x"))) assert.Equal(t, http.StatusBadRequest, rec.Code) assert.Empty(t, sub.jobs) assert.NotContains(t, buf.String(), "zebrakaka", "a file name is private content") } func TestUploadPost_ABodyCutOffInsideTheFilePartIsA400NotA500(t *testing.T) { app, sub := newUploadApp(t, audioOK, 200<<20) req := uploadReq(t, "t", "a.m4a", bytes.Repeat([]byte("a"), 4096)) raw, err := io.ReadAll(req.Body) require.NoError(t, err) req.Body = io.NopCloser(bytes.NewReader(raw[:len(raw)-2000])) // the sender hung up mid-file buf := &lockedBuf{} app.Log = slog.New(slog.NewTextHandler(buf, nil)) rec := do(t, app, req) assert.Equal(t, http.StatusBadRequest, rec.Code, "a connection that drops mid-upload is the sender's to retry, not a server fault") assert.Empty(t, sub.jobs) assert.NotContains(t, buf.String(), "level=ERROR") } func TestUploadPost_BidirectionalControlCharactersAreStrippedFromATitle(t *testing.T) { // U+202E reverses the text that follows it, so a title could render reversed on its // owner's own page (second adversarial review). app, sub := newUploadApp(t, audioOK, 200<<20) rec := do(t, app, uploadReq(t, "Sprint \u202Ereview \u2066x\u2069 \u200Fok", "a.m4a", []byte("x"))) require.Equal(t, http.StatusSeeOther, rec.Code) var title string require.NoError(t, rawPool(t).QueryRow(context.Background(), `SELECT title FROM uploaded_transcripts WHERE id = $1`, sub.jobs[0].UploadID).Scan(&title)) for _, r := range []string{"\u202E", "\u2066", "\u2069", "\u200F"} { assert.NotContains(t, title, r) } assert.Contains(t, title, "Sprint") assert.Contains(t, title, "review") } func TestUploadPost_SwedishLettersAndEmojiSurviveTheTitleCleaning(t *testing.T) { app, sub := newUploadApp(t, audioOK, 200<<20) // Includes a ZWJ sequence (woman + ZWJ + laptop) and a ZWNJ: joiners are not direction controls. rec := do(t, app, uploadReq(t, "Möte åäö 🎙️ 👩\u200d💻 a\u200cb", "a.m4a", []byte("x"))) require.Equal(t, http.StatusSeeOther, rec.Code) var title string require.NoError(t, rawPool(t).QueryRow(context.Background(), `SELECT title FROM uploaded_transcripts WHERE id = $1`, sub.jobs[0].UploadID).Scan(&title)) assert.Equal(t, "Möte åäö 🎙️ 👩\u200d💻 a\u200cb", title, "joiners and variation selectors belong to the text") }