package uploads_test import ( "context" "errors" "fmt" "io" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "git.d-ma.be/mathias/tapir/internal/domain" "git.d-ma.be/mathias/tapir/internal/uploads" ) // A full queue is the user's to retry, not a server fault: a typed rejection, the row // failed honestly, the audio removed (adversarial review F6). func TestAccept_AFullQueueIsABusyRejectionNotAServerErrorAndLeavesNothingBehind(t *testing.T) { r := newRig(t) r.sub.err = uploads.ErrQueueFull _, err := r.in.Accept(context.Background(), "user-1", "t", strings.NewReader("data")) var rej *uploads.RejectError require.True(t, errors.As(err, &rej), "a full queue must be a rejection the web layer can answer with 429: %v", err) assert.Equal(t, uploads.ReasonBusy, rej.Reason) assert.Contains(t, rej.Message, "waiting") assert.Equal(t, domain.UploadErrBusy, r.st.failed["up-1"], "the row must not sit queued: that is the stuck spinner") assert.Empty(t, r.tmpFiles(t), "the audio is deleted") } // roomSubmitter is a Submitter that can say, before anything is spooled, that there is no room. type roomSubmitter struct { fakeSubmitter room bool } func (r *roomSubmitter) HasRoom(string) bool { return r.room } func TestAccept_ANoRoomQueueIsRefusedBeforeAnythingIsSpooledProbedOrStored(t *testing.T) { // Checking only at Submit meant 200 MB was spooled and ffprobe run for every refused // attempt, and a failed row was left behind each time (second adversarial review). r := newRig(t) full := &roomSubmitter{room: false} in := uploads.NewIntake(r.st, r.pr, full, uploads.Limits{MaxBytes: 1024, MaxDuration: 90 * time.Minute}, r.tmp) _, err := in.Accept(context.Background(), "user-1", "t", strings.NewReader("data")) var rej *uploads.RejectError require.True(t, errors.As(err, &rej)) assert.Equal(t, uploads.ReasonBusy, rej.Reason) assert.Empty(t, r.st.created, "no row for an upload that was never accepted") assert.Empty(t, r.pr.paths, "ffprobe was never run on it") assert.Empty(t, r.tmpFiles(t), "nothing was spooled") assert.Empty(t, full.jobs) } func TestAccept_ASubmitterThatCannotSayStillWorksAsBefore(t *testing.T) { r := newRig(t) _, err := r.in.Accept(context.Background(), "user-1", "t", strings.NewReader("data")) require.NoError(t, err, "a Submitter without HasRoom is accepted: the check at Submit remains") } // brokenBody is a client body that fails mid-read, like a connection that dropped. type brokenBody struct{ n int } func (b *brokenBody) Read(p []byte) (int, error) { if b.n > 0 { b.n = 0 return copy(p, "partial audio"), nil } return 0, io.ErrUnexpectedEOF } func TestAccept_AnErrorReadingTheClientsBodyIsMarkedAsTheClientsAndNothingElseIs(t *testing.T) { r := newRig(t) _, err := r.in.Accept(context.Background(), "user-1", "t", &brokenBody{n: 1}) require.Error(t, err) assert.ErrorIs(t, err, uploads.ErrClientRead, "a dropped client body is the sender's to retry") assert.Empty(t, r.tmpFiles(t)) // A fault that is ours must NOT carry the marker, even if it wraps the same sentinel. r2 := newRig(t) r2.sub.err = fmt.Errorf("db: %w", io.ErrUnexpectedEOF) _, err = r2.in.Accept(context.Background(), "user-1", "t", strings.NewReader("data")) require.Error(t, err) assert.NotErrorIs(t, err, uploads.ErrClientRead) }