laravel-webrtc/tests/FileRecordingStoreTest.php

64 lines
1.8 KiB
PHP
Raw Permalink Normal View History

feat: full package — typed rooms (public/private/presence/open-presence) + call recording Rooms - Rooms\RoomType derived from the name prefix (like laravel-websockets channels): public, private-* (authorized), presence-* (authorized + member list), open-presence-* (public + member list). Room/RoomManager carry per-member info. - Contracts\RoomAuthorizer + secure DefaultRoomAuthorizer (denies private/presence until the host binds one). Presence rooms broadcast the member list + info. Recording (store the FULL call audio, in PHP, today) - Pure-P2P audio is DTLS-SRTP end-to-end, so the server can't capture it. Instead the browser MediaRecords its mic and streams chunks over the WS as BINARY frames (needs the new laravel-ws onBinary); RelaySignalingHandler appends them per participant via a RecordingStore. Recording\FileRecordingStore -> <room>/<peer>.webm. welcome/joined carry a record flag telling the client to stream. - Contracts\CallEventListener call-log seam (peer/room/recording lifecycle) so calls are auditable even when unrecorded. Wiring - RelaySignalingHandler(authorizer, recorder, events); webrtc:serve + provider + config/webrtc.php (recording enabled/path/format, authorizer, events). Tests 35/99 incl on-disk FileRecordingStore, presence member lists, auth gating, binary recording+finalize+events, RoomType, and the real WS round-trip. README documents room types + the browser MediaRecorder->WS recording flow. rel learn-atc #1060 #1055 #1057 #1051 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 07:01:12 +00:00
<?php
declare(strict_types=1);
namespace Blax\WebRtc\Tests;
use Blax\WebRtc\Recording\FileRecordingStore;
use PHPUnit\Framework\TestCase;
final class FileRecordingStoreTest extends TestCase
{
private string $dir;
protected function setUp(): void
{
$this->dir = sys_get_temp_dir().'/blax-webrtc-rec-'.bin2hex(random_bytes(4));
}
protected function tearDown(): void
{
if (! is_dir($this->dir)) {
return;
}
foreach (glob($this->dir.'/*/*') ?: [] as $file) {
@unlink($file);
}
foreach (glob($this->dir.'/*') ?: [] as $sub) {
@rmdir($sub);
}
@rmdir($this->dir);
}
public function test_appends_chunks_and_finalizes_the_full_audio(): void
{
$store = new FileRecordingStore($this->dir, 'webm');
$store->append('presence-lobby', 'peer1', 'CHUNK-A');
$store->append('presence-lobby', 'peer1', 'CHUNK-B');
$path = $store->finalize('presence-lobby', 'peer1');
$this->assertNotNull($path);
$this->assertFileExists($path);
$this->assertStringEndsWith('.webm', $path);
$this->assertSame('CHUNK-ACHUNK-B', file_get_contents($path));
}
public function test_finalize_without_any_data_returns_null(): void
{
$this->assertNull((new FileRecordingStore($this->dir))->finalize('r', 'p'));
}
public function test_room_and_peer_names_are_sanitized_into_a_contained_path(): void
{
$store = new FileRecordingStore($this->dir);
$store->append('room/../x', 'peer id!', 'X');
$path = $store->finalize('room/../x', 'peer id!');
$this->assertNotNull($path);
$this->assertStringStartsWith($this->dir, $path); // can't escape the recording dir
$this->assertFileExists($path);
}
}