<?phpnamespace Tests;use App\Scene\LinkScene;use JumPHP\Testing\GameTestCase;use JumPHP\WebRtc\Backend;use JumPHP\WebRtc\DataChannel;use JumPHP\WebRtc\WebRtc;/** Peer and DataChannel against a fake backend (what the sidecar / the page would answer): commands out, events in. */final class WebRtcTest extends GameTestCase{ private FakeBackend $fake; private WebRtc $webrtc; protected function setUp(): void { parent::setUp(); $this->game->start(LinkScene::class); $this->webrtc = $this->game->peek('webrtc'); // the scene's WebRtc service $this->webrtc->setBackend($this->fake = new FakeBackend()); } public function testAnOfferBecomesASignalForTheOtherSide(): void { $peer = $this->webrtc->peer(['stun' => 'stun:example.org:3478']); $signals = []; $peer->on('signal', function (array $s) use (&$signals): void { $signals[] = $s; }); $peer->channel('chat'); $peer->offer(); self::assertSame(['peer', 'channel', 'offer'], array_column($this->fake->sent, 'op')); self::assertSame('stun:example.org:3478', $this->fake->sent[0]['stun']); $this->fake->emit(['ev' => 'offer', 'id' => $peer->id, 'sdp' => 'v=0']); $this->fake->emit(['ev' => 'ice', 'id' => $peer->id, 'candidate' => 'candidate:1 1 UDP 1 10.0.0.2 5000 typ host', 'mline' => 0]); self::assertSame([['type' => 'offer', 'sdp' => 'v=0'], ['candidate' => 'candidate:1 1 UDP 1 10.0.0.2 5000 typ host', 'mline' => 0]], $signals); } public function testTheOtherSidesSignalsGoToTheBackend(): void { $peer = $this->webrtc->peer(); $peer->signal(['type' => 'answer', 'sdp' => 'v=0']); $peer->signal(['candidate' => 'candidate:2', 'mline' => 1]); $peer->signal(['nonsense' => true]); $last = array_slice($this->fake->sent, -2); self::assertSame(['id' => $peer->id, 'op' => 'remote', 'type' => 'answer', 'sdp' => 'v=0'], $last[0]); self::assertSame('ice', $last[1]['op']); self::assertSame(1, $last[1]['mline']); } public function testAChannelOpenedByTheOtherSideArrivesWithItsMessages(): void { $peer = $this->webrtc->peer(); $got = []; $peer->on('channel', function (DataChannel $ch) use (&$got): void { $ch->on('message', function (string $data, bool $binary) use (&$got, $ch): void { $got[] = [$ch->label, $data, $binary]; }); }); $this->fake->emit(['ev' => 'open', 'id' => $peer->id, 'label' => 'files']); $this->fake->emit(['ev' => 'message', 'id' => $peer->id, 'label' => 'files', 'text' => 'hello']); $this->fake->emit(['ev' => 'message', 'id' => $peer->id, 'label' => 'files', 'b64' => base64_encode("\x00\x01")]); self::assertSame([['files', 'hello', false], ['files', "\x00\x01", true]], $got); $peer->channel('files')->sendBinary("\xff"); self::assertSame(['id' => $peer->id, 'op' => 'send', 'label' => 'files', 'b64' => '/w=='], end($this->fake->sent)); } public function testStatesAndErrorsReachThePeer(): void { $peer = $this->webrtc->peer(); $errors = []; $peer->on('error', function (string $e) use (&$errors): void { $errors[] = $e; }); $this->fake->emit(['ev' => 'state', 'id' => $peer->id, 'state' => 'connected']); self::assertTrue($peer->isConnected()); $this->fake->emit(['ev' => 'exit', 'message' => 'the WebRTC sidecar stopped']); self::assertSame(['the WebRTC sidecar stopped'], $errors); }}final class FakeBackend implements Backend{ /** @var list<array<string, mixed>> */ public array $sent = []; /** @var list<callable> */ private array $listeners = []; public function name(): string { return 'fake'; } public function isSupported(): bool { return true; } public function send(array $command): void { $this->sent[] = $command; } public function listen(callable $listener): void { $this->listeners[] = $listener; } public function poll(): void { } public function emit(array $event): void { foreach ($this->listeners as $fn) { $fn($event); } }}