sidecar = $theirs; $this->client = ControlClient::fromStream($ours); } private function sidecarSays(array ...$lines): void { foreach ($lines as $line) { fwrite($this->sidecar, json_encode($line)."\n"); } } private function sidecarHeard(): array { return json_decode((string) fgets($this->sidecar), true); } public function test_request_frames_json_lines_and_matches_the_reply_id(): void { $this->sidecarSays(['id' => 1, 'ok' => true, 'answer' => 'v=answer']); $reply = $this->client->request('add_peer', ['room' => 'r1', 'peer' => 'p1', 'offer' => 'v=0']); $this->assertSame('v=answer', $reply['answer']); $this->assertSame( ['id' => 1, 'cmd' => 'add_peer', 'room' => 'r1', 'peer' => 'p1', 'offer' => 'v=0'], $this->sidecarHeard(), ); } public function test_pushed_events_are_buffered_not_mistaken_for_replies(): void { $this->sidecarSays( ['event' => 'peer_left', 'room' => 'r1', 'peer' => 'p9', 'seconds' => 12.5], ['id' => 1, 'ok' => true], ); $this->client->request('ping'); $events = $this->client->drainEvents(); $this->assertCount(1, $events); $this->assertSame('peer_left', $events[0]['event']); $this->assertSame([], $this->client->drainEvents(), 'drain clears the buffer'); } public function test_ok_false_replies_throw_with_the_sidecar_error(): void { $this->sidecarSays(['id' => 1, 'ok' => false, 'error' => 'no such peer: r1/ghost']); $this->expectException(SidecarException::class); $this->expectExceptionMessage('no such peer: r1/ghost'); $this->client->request('mute_peer', ['room' => 'r1', 'peer' => 'ghost', 'muted' => true]); } public function test_a_closed_sidecar_throws_instead_of_hanging(): void { // Half-close: the sidecar stops sending (EOF on our reads) but still // accepts our write — exercising the read-path failure. stream_socket_shutdown($this->sidecar, STREAM_SHUT_WR); $this->expectException(SidecarException::class); $this->expectExceptionMessage('closed or timed out'); $this->client->request('ping'); } public function test_ids_increment_across_requests(): void { $this->sidecarSays(['id' => 1, 'ok' => true], ['id' => 2, 'ok' => true]); $this->client->request('ping'); $this->client->request('ping'); $this->assertSame(1, $this->sidecarHeard()['id']); $this->assertSame(2, $this->sidecarHeard()['id']); } }