Refresh list status over Reverb with polling fallback, clarify queued vs processing, streamline upload empty states, and seed an admin login.
164 lines
6.0 KiB
PHP
164 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Events\RecordingTranscriptionUpdated;
|
|
use App\Jobs\TranscribeRecording;
|
|
use App\Models\Recording;
|
|
use App\Models\User;
|
|
use App\Services\TranscriptionService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Laravel\Ai\Transcription;
|
|
use Tests\TestCase;
|
|
|
|
class TranscriptionBroadcastTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected User $user;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$this->user = User::factory()->create();
|
|
$this->actingAs($this->user);
|
|
}
|
|
|
|
public function test_report_progress_broadcasts_transcription_updated(): void
|
|
{
|
|
Event::fake([RecordingTranscriptionUpdated::class]);
|
|
|
|
$recording = Recording::query()->create([
|
|
'user_id' => $this->user->id,
|
|
'title' => 'Broadcast progress',
|
|
'original_filename' => 'progress.mp3',
|
|
'file_path' => 'recordings/progress.mp3',
|
|
'file_size_bytes' => 100,
|
|
'transcription_status' => 'processing',
|
|
'transcription_driver' => 'local',
|
|
'transcription_started_at' => now(),
|
|
]);
|
|
|
|
$recording->reportProgress('Transcribing locally…', 50);
|
|
|
|
Event::assertDispatched(RecordingTranscriptionUpdated::class, function (RecordingTranscriptionUpdated $event) use ($recording): bool {
|
|
return $event->recording->is($recording)
|
|
&& $event->broadcastWith()['percent'] === 50
|
|
&& $event->broadcastWith()['progress'] === 'Transcribing locally…';
|
|
});
|
|
}
|
|
|
|
public function test_successful_transcription_broadcasts_done_status(): void
|
|
{
|
|
Event::fake([RecordingTranscriptionUpdated::class]);
|
|
Storage::fake('local');
|
|
Storage::disk('local')->put('recordings/sample.mp3', 'fake-audio-bytes');
|
|
|
|
Transcription::fake(['Hello from the recorder.']);
|
|
|
|
$recording = Recording::query()->create([
|
|
'user_id' => $this->user->id,
|
|
'title' => 'Sample',
|
|
'original_filename' => 'sample.mp3',
|
|
'file_path' => 'recordings/sample.mp3',
|
|
'file_size_bytes' => 12,
|
|
'transcription_status' => 'pending',
|
|
'transcription_driver' => 'local',
|
|
'transcription_started_at' => now(),
|
|
]);
|
|
|
|
(new TranscribeRecording($recording))->handle(app(TranscriptionService::class));
|
|
|
|
Event::assertDispatched(RecordingTranscriptionUpdated::class, function (RecordingTranscriptionUpdated $event) use ($recording): bool {
|
|
$payload = $event->broadcastWith();
|
|
|
|
return $event->recording->is($recording)
|
|
&& $payload['status'] === 'done'
|
|
&& $payload['percent'] === 100
|
|
&& $payload['has_transcript'] === true
|
|
&& ($payload['word_count'] ?? 0) > 0;
|
|
});
|
|
}
|
|
|
|
public function test_failed_transcription_broadcasts_failure(): void
|
|
{
|
|
Event::fake([RecordingTranscriptionUpdated::class]);
|
|
Storage::fake('local');
|
|
Storage::disk('local')->put('recordings/bad.mp3', 'fake-audio-bytes');
|
|
|
|
Transcription::fake(function () {
|
|
throw new \RuntimeException('Provider unavailable');
|
|
});
|
|
|
|
$recording = Recording::query()->create([
|
|
'user_id' => $this->user->id,
|
|
'title' => 'Bad',
|
|
'original_filename' => 'bad.mp3',
|
|
'file_path' => 'recordings/bad.mp3',
|
|
'file_size_bytes' => 12,
|
|
'transcription_status' => 'pending',
|
|
'transcription_driver' => 'local',
|
|
'transcription_started_at' => now(),
|
|
]);
|
|
|
|
try {
|
|
(new TranscribeRecording($recording))->handle(app(TranscriptionService::class));
|
|
$this->fail('Expected transcription to throw');
|
|
} catch (\RuntimeException $e) {
|
|
$this->assertSame('Provider unavailable', $e->getMessage());
|
|
}
|
|
|
|
Event::assertDispatched(RecordingTranscriptionUpdated::class, function (RecordingTranscriptionUpdated $event) use ($recording): bool {
|
|
$payload = $event->broadcastWith();
|
|
|
|
return $event->recording->is($recording)
|
|
&& $payload['status'] === 'failed'
|
|
&& $payload['error'] === 'Provider unavailable';
|
|
});
|
|
}
|
|
|
|
public function test_cancel_transcription_broadcasts_cancelled_status(): void
|
|
{
|
|
Event::fake([RecordingTranscriptionUpdated::class]);
|
|
|
|
$recording = Recording::query()->create([
|
|
'user_id' => $this->user->id,
|
|
'title' => 'Cancel me',
|
|
'original_filename' => 'cancel.mp3',
|
|
'file_path' => 'recordings/cancel.mp3',
|
|
'file_size_bytes' => 100,
|
|
'transcription_status' => 'processing',
|
|
'transcription_driver' => 'local',
|
|
'transcription_started_at' => now(),
|
|
]);
|
|
|
|
$recording->cancelTranscription();
|
|
|
|
Event::assertDispatched(RecordingTranscriptionUpdated::class, function (RecordingTranscriptionUpdated $event) use ($recording): bool {
|
|
return $event->recording->is($recording)
|
|
&& $event->broadcastWith()['status'] === 'cancelled';
|
|
});
|
|
}
|
|
|
|
public function test_broadcast_event_uses_private_recording_channel(): void
|
|
{
|
|
$recording = Recording::query()->create([
|
|
'user_id' => $this->user->id,
|
|
'title' => 'Channel',
|
|
'original_filename' => 'channel.mp3',
|
|
'file_path' => 'recordings/channel.mp3',
|
|
'file_size_bytes' => 100,
|
|
'transcription_status' => 'pending',
|
|
]);
|
|
|
|
$event = new RecordingTranscriptionUpdated($recording);
|
|
|
|
$this->assertSame('RecordingTranscriptionUpdated', $event->broadcastAs());
|
|
$this->assertSame('private-recording.'.$recording->id, $event->broadcastOn()[0]->name);
|
|
$this->assertSame('private-user.'.$this->user->id.'.recordings', $event->broadcastOn()[1]->name);
|
|
}
|
|
}
|