test: third review -- a real disconnect is not an error, a DB drop is not the client's, a note stays visible, every bidi character and the 80% boundary pinned (red)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
|||||||
|
package web_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A client that hangs up in the middle of an upload is routine, not a server fault. The
|
||||||
|
// in-process recorder cannot see it (its context never cancels), so this talks to a real
|
||||||
|
// listener (third adversarial review: over a socket every such case logged an ERROR and
|
||||||
|
// half-close returned a 500, because the error page was rendered on a cancelled context).
|
||||||
|
|
||||||
|
func partialUpload(t *testing.T, addr string, hangUp func(*net.TCPConn)) {
|
||||||
|
t.Helper()
|
||||||
|
c, err := net.Dial("tcp", addr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
tc := c.(*net.TCPConn)
|
||||||
|
const boundary = "xBOUNDARYx"
|
||||||
|
head := "--" + boundary + "\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.m4a\"\r\nContent-Type: audio/mp4\r\n\r\n"
|
||||||
|
_, err = fmt.Fprintf(tc, "POST /uploads HTTP/1.1\r\nHost: x\r\nContent-Type: multipart/form-data; boundary=%s\r\nContent-Length: 200000\r\n\r\n%s%s",
|
||||||
|
boundary, head, strings.Repeat("a", 4000))
|
||||||
|
require.NoError(t, err)
|
||||||
|
time.Sleep(50 * time.Millisecond) // let the server start reading the part
|
||||||
|
hangUp(tc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadPost_AClientThatHangsUpMidFileIsLoggedAtInfoAndNeverAsAnError(t *testing.T) {
|
||||||
|
hangUps := map[string]func(*net.TCPConn){
|
||||||
|
"a half close": func(c *net.TCPConn) { _ = c.CloseWrite(); time.Sleep(100 * time.Millisecond); _ = c.Close() },
|
||||||
|
"a full close": func(c *net.TCPConn) { _ = c.Close() },
|
||||||
|
"a reset (RST)": func(c *net.TCPConn) { _ = c.SetLinger(0); _ = c.Close() },
|
||||||
|
}
|
||||||
|
for name, hangUp := range hangUps {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
app, sub := newUploadApp(t, audioOK, 200<<20)
|
||||||
|
buf := &lockedBuf{}
|
||||||
|
app.Log = slog.New(slog.NewTextHandler(buf, nil))
|
||||||
|
srv := httptest.NewServer(app.Router())
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
partialUpload(t, strings.TrimPrefix(srv.URL, "http://"), hangUp)
|
||||||
|
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
out := buf.String()
|
||||||
|
return strings.Contains(out, "upload abandoned") || strings.Contains(out, "upload rejected") || strings.Contains(out, "level=ERROR")
|
||||||
|
}, 3*time.Second, 20*time.Millisecond, "the server never finished handling the aborted upload")
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
out := buf.String()
|
||||||
|
assert.NotContains(t, out, "level=ERROR", "a client hanging up is not a server fault:\n%s", out)
|
||||||
|
assert.Empty(t, sub.jobs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadPost_ADatabaseDropThatLooksLikeAnEOFIsStillAServerError(t *testing.T) {
|
||||||
|
// pgx turns a connection that drops mid-message into io.ErrUnexpectedEOF. Mapping that
|
||||||
|
// sentinel to "your upload was cut off" (a 400) would hide a real fault: only an error
|
||||||
|
// from reading the CLIENT's body is theirs (third adversarial review).
|
||||||
|
app, _ := newUploadApp(t, audioOK, 200<<20)
|
||||||
|
buf := &lockedBuf{}
|
||||||
|
app.Log = slog.New(slog.NewTextHandler(buf, nil))
|
||||||
|
app.Uploads = failingUploader{err: fmt.Errorf("failed to receive message: %w", io.ErrUnexpectedEOF)}
|
||||||
|
|
||||||
|
rec := do(t, app, uploadReq(t, "t", "a.m4a", []byte("x")))
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||||
|
assert.Contains(t, buf.String(), "level=ERROR")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user