From 1b4747ed03fccb670f26865d0865a73e4dcad136 Mon Sep 17 00:00:00 2001 From: Mathias Date: Sat, 3 Oct 2026 21:12:34 +0200 Subject: [PATCH] test: FailUpload must not overwrite a finished upload; ffmpeg stderr is bounded, red (review of #55) Co-Authored-By: Claude Sonnet 5.5 --- internal/adapters/audio/ffmpeg_test.go | 12 +++++ .../uploaded_transcripts_failguard_test.go | 47 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 internal/adapters/store/uploaded_transcripts_failguard_test.go diff --git a/internal/adapters/audio/ffmpeg_test.go b/internal/adapters/audio/ffmpeg_test.go index e4bebb9..5e7dcbd 100644 --- a/internal/adapters/audio/ffmpeg_test.go +++ b/internal/adapters/audio/ffmpeg_test.go @@ -126,3 +126,15 @@ printf data > "$last"`) assert.Equal(t, "32", args[ms+1]) assert.Less(t, ms, in, "an input option placed after -i is applied to the output") } + +func TestFFmpegToWAV_ABoundedPartOfAFloodingStderrIsKept(t *testing.T) { + // A hostile mp3 made ffmpeg print ~0.4 bytes of stderr per input byte (83 MB for a + // 209 MB file), all of it buffered, then copied twice into the error (#49 review). + bin := stub(t, `head -c 5000000 /dev/zero | tr '\0' 'e' >&2; exit 1`) + + err := (&audio.FFmpeg{Bin: bin}).ToWAV(context.Background(), "/spool/in", filepath.Join(t.TempDir(), "o.wav")) + + require.Error(t, err) + assert.Less(t, len(err.Error()), 16*1024, "the error carries a bounded head of stderr, not all of it") + assert.Contains(t, err.Error(), "eeee", "and still carries some of it") +} diff --git a/internal/adapters/store/uploaded_transcripts_failguard_test.go b/internal/adapters/store/uploaded_transcripts_failguard_test.go new file mode 100644 index 0000000..c8ea345 --- /dev/null +++ b/internal/adapters/store/uploaded_transcripts_failguard_test.go @@ -0,0 +1,47 @@ +package store_test + +// A shutdown can record a job as failed after its transcript was already committed (the +// commit lands, the caller sees a cancelled context). FailUpload must not be able to turn a +// finished row into a failed one and hide the text (#50 review). + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "git.d-ma.be/mathias/tapir/internal/domain" +) + +func TestFailUpload_LeavesAFinishedUploadAsItWas(t *testing.T) { + s := newStore(t) + ctx := context.Background() + id := startedUpload(t, s) + require.NoError(t, s.StartTranscribing(ctx, userA, id)) + require.NoError(t, s.CompleteUpload(ctx, userA, id, domain.Transcription{Text: "synthetic text", Endpoint: "iguana/kb-whisper"})) + + _ = s.FailUpload(ctx, userA, id, domain.UploadErrInterrupted) + + got, err := s.GetUpload(ctx, userA, id) + require.NoError(t, err) + assert.Equal(t, domain.UploadDone, got.Status) + assert.Equal(t, "synthetic text", got.Content) + assert.Empty(t, got.ErrorKind) +} + +func TestFailUpload_StillFailsAQueuedOrTranscribingUpload(t *testing.T) { + s := newStore(t) + ctx := context.Background() + queued := startedUpload(t, s) + running, err := s.CreateUpload(ctx, userA, "r", false) + require.NoError(t, err) + require.NoError(t, s.StartTranscribing(ctx, userA, running)) + + for _, id := range []string{queued, running} { + require.NoError(t, s.FailUpload(ctx, userA, id, domain.UploadErrBusy)) + got, err := s.GetUpload(ctx, userA, id) + require.NoError(t, err) + assert.Equal(t, domain.UploadFailed, got.Status) + } +}