diff --git a/internal/uploads/worker_duration_test.go b/internal/uploads/worker_duration_test.go index a1c94d4..cae4b18 100644 --- a/internal/uploads/worker_duration_test.go +++ b/internal/uploads/worker_duration_test.go @@ -69,15 +69,84 @@ func TestWorkerDuration_JustOverTheLimitWithinTheSlackStillTranscribes(t *testin assert.Contains(t, completed, "a", "ffmpeg rounds: 20 s over a 90-minute limit is not a lie") } -func TestWorkerDuration_AFileThatEndsFarEarlierThanItClaimedIsRefusedNotHalfTranscribed(t *testing.T) { +func audioStepNote(r *wrig) string { + for _, st := range r.st.lastSteps() { + if st.Key == "audio" { + return st.Note + } + } + return "" +} + +func TestWorkerDuration_AFileThatEndsFarEarlierThanItClaimedIsStillTranscribedAndSaysSo(t *testing.T) { + // A container header can over-estimate a complete file (VBR MP3 without a Xing header + // claimed 79 minutes for 20 real ones). Refusing it was wrong; staying silent about a + // truncated one is wrong too. So it is transcribed, and the audio step carries a note. r := newWRig(t, time.Minute) w := durationWorker(t, r, 20*60, 90*time.Minute) - w.Process(context.Background(), jobWith(r, t, 60*time.Minute)) + w.Process(context.Background(), jobWith(r, t, 79*time.Minute)) - _, failed := r.st.snapshot() - assert.Equal(t, domain.UploadErrIncomplete, failed["a"], "a recording truncated at a third must not return a third of a transcript with no warning") - assert.Empty(t, r.tr.paths) + completed, failed := r.st.snapshot() + assert.Empty(t, failed, "a complete recording with a bad header estimate must not be refused") + assert.Contains(t, completed, "a") + note := audioStepNote(r) + assert.Contains(t, note, "20 min", "what was really transcribed") + assert.Contains(t, note, "1 h 19 min", "what the file claimed") + assert.Contains(t, note, "cut off", "and what to do if that is not a surprise") +} + +func TestWorkerDuration_TheShortfallNoteIsPinnedAtEightyPercentAndTwoMinutes(t *testing.T) { + tests := []struct { + name string + claimed time.Duration + wav float64 + noted bool + }{ + {"79 percent of an hour", 60 * time.Minute, 0.79 * 3600, true}, + {"81 percent of an hour", 60 * time.Minute, 0.81 * 3600, false}, + {"exactly two minutes short and under 80 percent", 6 * time.Minute, 4 * 60, false}, + {"two minutes and a second short and under 80 percent", 6 * time.Minute, 4*60 - 1, true}, + {"unknown claimed length", 0, 20 * 60, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := newWRig(t, time.Minute) + w := durationWorker(t, r, tt.wav, 90*time.Minute) + + w.Process(context.Background(), jobWith(r, t, tt.claimed)) + + assert.Equal(t, tt.noted, audioStepNote(r) != "") + completed, _ := r.st.snapshot() + assert.Contains(t, completed, "a", "a note never stops the job") + }) + } +} + +func TestWorkerDuration_TheTooLongToleranceIsExactlyThirtySeconds(t *testing.T) { + tests := []struct { + name string + wav float64 + fail bool + }{ + {"at the limit plus 30 s", 90*60 + 30, false}, + {"at the limit plus 31 s", 90*60 + 31, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := newWRig(t, time.Minute) + w := durationWorker(t, r, tt.wav, 90*time.Minute) + + w.Process(context.Background(), jobWith(r, t, 90*time.Minute)) + + _, failed := r.st.snapshot() + if tt.fail { + assert.Equal(t, domain.UploadErrTooLong, failed["a"]) + } else { + assert.Empty(t, failed) + } + }) + } } func TestWorkerDuration_AnEstimateThatIsMerelyALittleOffIsNotTruncation(t *testing.T) {