laravel-webrtc/tests/RustMediaEngineTest.php

131 lines
4.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Blax\WebRtc\Tests;
use Blax\WebRtc\Media\Rust\BinaryManager;
use Blax\WebRtc\Media\Rust\ControlClient;
use Blax\WebRtc\Media\Rust\SidecarException;
use Blax\WebRtc\Media\Rust\SidecarSupervisor;
use Blax\WebRtc\Media\RustMediaEngine;
use PHPUnit\Framework\TestCase;
final class RustMediaEngineTest extends TestCase
{
/** @var resource */
private $sidecar;
private RustMediaEngine $engine;
protected function setUp(): void
{
[$theirs, $ours] = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
$this->sidecar = $theirs;
$supervisor = new SidecarSupervisor(new BinaryManager([]), ['auto_spawn' => false]);
$this->engine = new RustMediaEngine($supervisor, ControlClient::fromStream($ours));
}
private function sidecarSays(array $line): void
{
fwrite($this->sidecar, json_encode($line)."\n");
}
private function sidecarHeard(): array
{
return json_decode((string) fgets($this->sidecar), true);
}
public function test_add_peer_speaks_the_control_protocol(): void
{
$this->sidecarSays(['id' => 1, 'ok' => true, 'answer' => 'v=answer']);
$answer = $this->engine->addPeer('lobby', 'alice', 'v=offer', '/rec/alice.ogg');
$this->assertSame('v=answer', $answer);
$this->assertSame(
[
'id' => 1,
'cmd' => 'add_peer',
'room' => 'lobby',
'peer' => 'alice',
'offer' => 'v=offer',
'record' => '/rec/alice.ogg',
],
$this->sidecarHeard(),
);
}
public function test_media_engine_contract_splits_composite_peer_ids(): void
{
$this->sidecarSays(['id' => 1, 'ok' => true, 'answer' => 'v=a']);
$this->engine->offer('lobby/alice', 'v=offer');
$heard = $this->sidecarHeard();
$this->assertSame(['lobby', 'alice'], [$heard['room'], $heard['peer']]);
$this->assertArrayNotHasKey('record', $heard);
$this->sidecarSays(['id' => 2, 'ok' => true]);
$this->engine->startRecording('lobby/alice', '/rec/alice.ogg');
$heard = $this->sidecarHeard();
$this->assertSame('record_start', $heard['cmd']);
$this->assertSame('/rec/alice.ogg', $heard['path']);
$this->sidecarSays(['id' => 3, 'ok' => true]);
$this->engine->close('lobby/alice');
$this->assertSame('remove_peer', $this->sidecarHeard()['cmd']);
}
public function test_bare_peer_ids_fall_back_to_the_default_room(): void
{
$this->sidecarSays(['id' => 1, 'ok' => true, 'answer' => 'v=a']);
$this->engine->offer('alice', 'v=offer');
$heard = $this->sidecarHeard();
$this->assertSame(['default', 'alice'], [$heard['room'], $heard['peer']]);
}
public function test_mute_stats_and_events(): void
{
$this->sidecarSays(['id' => 1, 'ok' => true]);
$this->engine->mutePeer('lobby', 'alice', true);
$this->assertTrue($this->sidecarHeard()['muted']);
$this->sidecarSays([
'id' => 2,
'ok' => true,
'rooms' => [['room' => 'lobby', 'peers' => [['peer' => 'alice', 'connected' => true, 'muted' => true, 'seconds' => 4.2]]]],
]);
$stats = $this->engine->stats();
$this->assertSame('lobby', $stats[0]['room']);
fwrite($this->sidecar, json_encode(['event' => 'peer_connected', 'room' => 'lobby', 'peer' => 'bob'])."\n");
$this->sidecarSays(['id' => 3, 'ok' => true]);
$this->engine->removePeer('lobby', 'bob');
$this->assertSame('peer_connected', $this->engine->drainEvents()[0]['event']);
}
public function test_ice_candidates_and_connect_peers_are_deliberate_noops(): void
{
// Neither may touch the socket: str0m learns remote candidates from
// STUN, and room membership already routes media.
$this->engine->addIceCandidate('lobby/alice', ['candidate' => 'x']);
$this->engine->connectPeers('lobby/alice', 'lobby/bob');
stream_set_blocking($this->sidecar, false);
$this->assertFalse(fgets($this->sidecar), 'no bytes were written to the sidecar');
}
public function test_bridge_is_not_implemented_in_the_data_plane_yet(): void
{
$this->expectException(SidecarException::class);
$this->expectExceptionMessage('OpenAiRealtimeBridge');
$this->engine->bridge('lobby/alice', ['model' => 'gpt-realtime']);
}
public function test_engine_name(): void
{
$this->assertSame('rust-sfu', $this->engine->name());
}
}