Auto-queue transcription on upload and clarify pending status.

Batch uploads were only storing files as pending without dispatching Whisper jobs; queue them immediately, add a bulk pending action, and show human-readable status labels.
This commit is contained in:
ben
2026-08-12 16:26:36 +02:00
parent 3498861184
commit 1dcdfc0ed0
12 changed files with 525 additions and 95 deletions
+93 -6
View File
@@ -23,22 +23,26 @@ class RecordingUploadTest extends TestCase
'original_filename' => 'note.mp3',
'file_path' => 'recordings/note.mp3',
'file_size_bytes' => 1024,
'transcription_status' => 'pending',
'transcription_status' => 'done',
'transcript' => 'One two three four five',
]);
$this->get(route('recordings.index'))
->assertOk()
->assertSee('Pocket note');
->assertSee('Pocket note')
->assertSee('Words')
->assertSee('5');
}
public function test_user_can_upload_an_mp3(): void
{
Storage::fake('local');
Bus::fake();
$file = UploadedFile::fake()->create('meeting.mp3', 500, 'audio/mpeg');
$response = $this->post(route('recordings.store'), [
'audio' => $file,
'audio' => [$file],
'title' => 'Team meeting',
]);
@@ -48,12 +52,48 @@ class RecordingUploadTest extends TestCase
$response->assertRedirect(route('recordings.show', $recording));
$this->assertSame('Team meeting', $recording->title);
$this->assertSame('pending', $recording->transcription_status);
$this->assertSame('local', $recording->transcription_driver);
$this->assertNotNull($recording->transcription_started_at);
Storage::disk('local')->assertExists($recording->file_path);
Bus::assertDispatched(TranscribeRecording::class);
}
public function test_user_can_batch_upload_multiple_audio_files(): void
{
Storage::fake('local');
Bus::fake();
$response = $this->post(route('recordings.store'), [
'audio' => [
UploadedFile::fake()->create('one.mp3', 400, 'audio/mpeg'),
UploadedFile::fake()->create('two.wav', 400, 'audio/wav'),
UploadedFile::fake()->create('three.ogg', 400, 'audio/ogg'),
],
]);
$response->assertRedirect(route('recordings.index'));
$response->assertSessionHas('success');
$this->assertSame(3, Recording::query()->count());
Bus::assertDispatched(TranscribeRecording::class, 3);
foreach (Recording::query()->get() as $recording) {
Storage::disk('local')->assertExists($recording->file_path);
$this->assertSame('pending', $recording->transcription_status);
}
}
public function test_upload_page_includes_dropzone(): void
{
$this->get(route('recordings.create'))
->assertOk()
->assertSee('Drop audio files here')
->assertSee('name="audio[]"', false);
}
public function test_user_can_upload_wav_and_ogg(): void
{
Storage::fake('local');
Bus::fake();
foreach ([
['memo.wav', 'audio/wav'],
@@ -61,7 +101,7 @@ class RecordingUploadTest extends TestCase
['talk.m4a', 'audio/mp4'],
] as [$name, $mime]) {
$response = $this->post(route('recordings.store'), [
'audio' => UploadedFile::fake()->create($name, 400, $mime),
'audio' => [UploadedFile::fake()->create($name, 400, $mime)],
'title' => $name,
]);
@@ -71,6 +111,8 @@ class RecordingUploadTest extends TestCase
$response->assertRedirect(route('recordings.show', $recording));
Storage::disk('local')->assertExists($recording->file_path);
}
Bus::assertDispatched(TranscribeRecording::class, 3);
}
public function test_unsupported_audio_type_is_rejected(): void
@@ -79,9 +121,9 @@ class RecordingUploadTest extends TestCase
$this->from(route('recordings.create'))
->post(route('recordings.store'), [
'audio' => UploadedFile::fake()->create('notes.txt', 10, 'text/plain'),
'audio' => [UploadedFile::fake()->create('notes.txt', 10, 'text/plain')],
])
->assertSessionHasErrors('audio');
->assertSessionHasErrors(['audio.0']);
}
public function test_user_can_queue_local_transcription(): void
@@ -385,4 +427,49 @@ class RecordingUploadTest extends TestCase
$this->assertSame('pending', $recording->transcription_status);
Bus::assertDispatched(TranscribeRecording::class);
}
public function test_user_can_queue_all_pending_transcriptions(): void
{
Bus::fake();
Recording::query()->create([
'title' => 'Needs work',
'original_filename' => 'needs.mp3',
'file_path' => 'recordings/needs.mp3',
'file_size_bytes' => 100,
'transcription_status' => 'pending',
]);
Recording::query()->create([
'title' => 'Already done',
'original_filename' => 'done.mp3',
'file_path' => 'recordings/done.mp3',
'file_size_bytes' => 100,
'transcription_status' => 'done',
'transcript' => 'Finished text',
]);
$this->from(route('recordings.index'))
->post(route('recordings.transcribe-pending'))
->assertRedirect(route('recordings.index'))
->assertSessionHas('success');
Bus::assertDispatched(TranscribeRecording::class, 1);
}
public function test_index_shows_human_status_labels(): void
{
Recording::query()->create([
'title' => 'Label check',
'original_filename' => 'label.mp3',
'file_path' => 'recordings/label.mp3',
'file_size_bytes' => 100,
'transcription_status' => 'pending',
]);
$this->get(route('recordings.index'))
->assertOk()
->assertSee('Queued')
->assertDontSee('>pending<', false);
}
}