120 lines
3.5 KiB
PHP
120 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Recording;
|
|
use App\Models\User;
|
|
use App\Services\TranscriptionService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Process;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Tests\TestCase;
|
|
|
|
class LocalWhisperCurlTransportTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_curl_transport_streams_sse_from_real_http_endpoint(): void
|
|
{
|
|
if (! function_exists('curl_init')) {
|
|
$this->markTestSkipped('curl extension required');
|
|
}
|
|
|
|
Storage::fake('local');
|
|
Storage::disk('local')->put('recordings/large.mp3', str_repeat('A', 2_000_000));
|
|
|
|
$stub = base_path('tests/fixtures/whisper-sse-stub.php');
|
|
$host = '127.0.0.1';
|
|
$port = $this->reservePort($host);
|
|
$docRoot = base_path('tests/fixtures');
|
|
|
|
$server = Process::timeout(30)
|
|
->path($docRoot)
|
|
->start([
|
|
PHP_BINARY,
|
|
'-S',
|
|
"{$host}:{$port}",
|
|
'whisper-sse-stub.php',
|
|
]);
|
|
|
|
try {
|
|
$this->waitForServer($host, $port);
|
|
|
|
config([
|
|
'ai.local_whisper_transport' => 'curl',
|
|
'ai.providers.local-whisper.url' => "http://{$host}:{$port}/v1",
|
|
'ai.providers.local-whisper.key' => 'test-key',
|
|
]);
|
|
|
|
$user = User::factory()->create();
|
|
$recording = Recording::query()->create([
|
|
'user_id' => $user->id,
|
|
'title' => 'Curl transport',
|
|
'original_filename' => 'large.mp3',
|
|
'file_path' => 'recordings/large.mp3',
|
|
'file_size_bytes' => 2_000_000,
|
|
'duration_seconds' => 40,
|
|
'transcription_status' => 'processing',
|
|
'transcription_driver' => 'local',
|
|
]);
|
|
|
|
$partials = [];
|
|
|
|
$text = app(TranscriptionService::class)->transcribe(
|
|
$recording,
|
|
function (string $message, int $percent, ?string $partial = null) use (&$partials): void {
|
|
if ($partial !== null) {
|
|
$partials[] = $partial;
|
|
}
|
|
},
|
|
);
|
|
|
|
$this->assertSame('Hello from whisper.', $text);
|
|
$this->assertContains('Hello ', $partials);
|
|
$this->assertContains('Hello from whisper.', $partials);
|
|
} finally {
|
|
$server->signal(SIGTERM);
|
|
$server->wait();
|
|
}
|
|
}
|
|
|
|
private function reservePort(string $host): int
|
|
{
|
|
$socket = stream_socket_server("tcp://{$host}:0");
|
|
|
|
if ($socket === false) {
|
|
$this->fail('Unable to reserve a local TCP port for the Whisper stub.');
|
|
}
|
|
|
|
$name = stream_socket_get_name($socket, false);
|
|
fclose($socket);
|
|
|
|
if (! is_string($name) || ! str_contains($name, ':')) {
|
|
$this->fail('Unable to determine reserved port.');
|
|
}
|
|
|
|
return (int) substr($name, strrpos($name, ':') + 1);
|
|
}
|
|
|
|
private function waitForServer(string $host, int $port): void
|
|
{
|
|
$deadline = microtime(true) + 5;
|
|
|
|
while (microtime(true) < $deadline) {
|
|
$errno = 0;
|
|
$errstr = '';
|
|
$fp = @fsockopen($host, $port, $errno, $errstr, 0.2);
|
|
|
|
if (is_resource($fp)) {
|
|
fclose($fp);
|
|
|
|
return;
|
|
}
|
|
|
|
usleep(50_000);
|
|
}
|
|
|
|
$this->fail("Whisper stub server did not start on {$host}:{$port}");
|
|
}
|
|
}
|