Harden stuck Whisper recovery and record transcription duration.
Fail jobs on timeout, treat stale reserved queue rows as orphans, skip migrate/seed on queue/reverb boot, and store how long each successful run took.
This commit is contained in:
@@ -13,6 +13,7 @@ use App\Services\TranscriptionService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Laravel\Ai\Transcription;
|
||||
use Livewire\Livewire;
|
||||
@@ -209,6 +210,8 @@ class RecordingUploadTest extends TestCase
|
||||
|
||||
Transcription::fake(['Hello from the recorder.']);
|
||||
|
||||
$this->freezeTime();
|
||||
|
||||
$recording = Recording::query()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'title' => 'Sample',
|
||||
@@ -217,6 +220,7 @@ class RecordingUploadTest extends TestCase
|
||||
'file_size_bytes' => 12,
|
||||
'transcription_status' => 'pending',
|
||||
'transcription_driver' => 'local',
|
||||
'transcription_started_at' => now()->subSeconds(42),
|
||||
]);
|
||||
|
||||
(new TranscribeRecording($recording))->handle(app(TranscriptionService::class));
|
||||
@@ -227,6 +231,8 @@ class RecordingUploadTest extends TestCase
|
||||
$this->assertSame(100, $recording->transcription_percent);
|
||||
$this->assertSame('Transcription complete', $recording->transcription_progress);
|
||||
$this->assertNotNull($recording->transcribed_at);
|
||||
$this->assertSame(42, $recording->transcription_duration_seconds);
|
||||
$this->assertSame('42s', $recording->transcriptionStatusPayload()['transcription_duration_human']);
|
||||
}
|
||||
|
||||
public function test_transcription_job_stores_error_on_failure(): void
|
||||
@@ -283,7 +289,70 @@ class RecordingUploadTest extends TestCase
|
||||
|
||||
$recording->refresh();
|
||||
$this->assertSame('failed', $recording->transcription_status);
|
||||
$this->assertStringContainsString('worker stopped', $recording->transcription_error);
|
||||
$this->assertStringContainsString('timed out', $recording->transcription_error);
|
||||
}
|
||||
|
||||
public function test_stale_reserved_job_is_treated_as_orphaned(): void
|
||||
{
|
||||
$recording = Recording::query()->create([
|
||||
'user_id' => $this->user->id,
|
||||
'title' => 'Wedged whisper',
|
||||
'original_filename' => 'wedged.mp3',
|
||||
'file_path' => 'recordings/wedged.mp3',
|
||||
'file_size_bytes' => 100,
|
||||
'transcription_status' => 'processing',
|
||||
'transcription_progress' => 'Transcribing locally…',
|
||||
'transcription_percent' => 50,
|
||||
'transcription_driver' => 'local',
|
||||
'transcription_started_at' => now()->subMinutes(20),
|
||||
'updated_at' => now()->subMinutes(20),
|
||||
]);
|
||||
|
||||
$job = new TranscribeRecording($recording);
|
||||
$payload = json_encode([
|
||||
'displayName' => TranscribeRecording::class,
|
||||
'data' => [
|
||||
'command' => serialize($job),
|
||||
],
|
||||
], JSON_THROW_ON_ERROR);
|
||||
|
||||
DB::table('jobs')->insert([
|
||||
'queue' => 'default',
|
||||
'payload' => $payload,
|
||||
'attempts' => 1,
|
||||
'reserved_at' => now()->subMinutes(15)->timestamp,
|
||||
'available_at' => now()->subMinutes(20)->timestamp,
|
||||
'created_at' => now()->subMinutes(20)->timestamp,
|
||||
]);
|
||||
|
||||
$this->assertFalse($recording->hasActiveTranscriptionJob());
|
||||
$this->assertTrue($recording->isOrphanedTranscription());
|
||||
|
||||
$deleted = $recording->discardQueuedTranscriptionJobs();
|
||||
$this->assertSame(1, $deleted, 'stale reserved job should be discarded');
|
||||
$this->assertDatabaseCount('jobs', 0);
|
||||
|
||||
// Put the stale job back to exercise status-poll recovery.
|
||||
DB::table('jobs')->insert([
|
||||
'queue' => 'default',
|
||||
'payload' => $payload,
|
||||
'attempts' => 1,
|
||||
'reserved_at' => now()->subMinutes(15)->timestamp,
|
||||
'available_at' => now()->subMinutes(20)->timestamp,
|
||||
'created_at' => now()->subMinutes(20)->timestamp,
|
||||
]);
|
||||
$recording->forceFill([
|
||||
'transcription_status' => 'processing',
|
||||
'transcription_error' => null,
|
||||
])->save();
|
||||
|
||||
$this->getJson(route('recordings.transcription-status', $recording))
|
||||
->assertOk()
|
||||
->assertJsonPath('status', 'failed');
|
||||
|
||||
$this->assertDatabaseCount('jobs', 0);
|
||||
$recording->refresh();
|
||||
$this->assertSame('failed', $recording->transcription_status);
|
||||
}
|
||||
|
||||
public function test_recent_processing_is_not_marked_orphaned(): void
|
||||
|
||||
Reference in New Issue
Block a user