handle([ 'type' => 'offer', 'peer' => 'p1', 'sdp' => 'OFFER-SDP', ]); $this->assertSame(['type' => 'answer', 'peer' => 'p1', 'sdp' => 'answer-for-p1'], $result); $this->assertSame(['offer', 'p1', 'OFFER-SDP'], $engine->calls[0]); } public function test_ice_is_forwarded_and_needs_no_reply(): void { $engine = new FakeMediaEngine; $result = (new SignalingHandler($engine))->handle([ 'type' => 'ice', 'peer' => 'p1', 'candidate' => ['candidate' => 'a=x'], ]); $this->assertNull($result); $this->assertSame(['ice', 'p1', ['candidate' => 'a=x']], $engine->calls[0]); } public function test_record_connect_bridge_close_are_acked_and_routed(): void { $engine = new FakeMediaEngine; $handler = new SignalingHandler($engine); $this->assertSame(['type' => 'ack', 'peer' => 'p1'], $handler->handle(['type' => 'record', 'peer' => 'p1', 'path' => '/tmp/a.opus'])); $this->assertSame(['type' => 'ack', 'peer' => 'p1'], $handler->handle(['type' => 'connect', 'peer' => 'p1', 'other' => 'p2'])); $this->assertSame(['type' => 'ack', 'peer' => 'p1'], $handler->handle(['type' => 'bridge', 'peer' => 'p1', 'options' => ['model' => 'gpt-realtime']])); $this->assertSame(['type' => 'ack', 'peer' => 'p1'], $handler->handle(['type' => 'close', 'peer' => 'p1'])); $this->assertSame(['record', 'p1', '/tmp/a.opus'], $engine->calls[0]); $this->assertSame(['connect', 'p1', 'p2'], $engine->calls[1]); $this->assertSame(['bridge', 'p1', ['model' => 'gpt-realtime']], $engine->calls[2]); $this->assertSame(['close', 'p1'], $engine->calls[3]); } public function test_a_missing_peer_is_an_error(): void { $result = (new SignalingHandler(new FakeMediaEngine))->handle(['type' => 'offer']); $this->assertSame('error', $result['type']); } public function test_an_unknown_type_is_an_error(): void { $result = (new SignalingHandler(new FakeMediaEngine))->handle(['type' => 'wat', 'peer' => 'p1']); $this->assertSame('error', $result['type']); $this->assertStringContainsString('wat', $result['error']); } public function test_an_engine_exception_is_caught_as_an_error_reply(): void { $engine = new FakeMediaEngine; $engine->throwOnOffer = new \RuntimeException('boom'); $result = (new SignalingHandler($engine))->handle(['type' => 'offer', 'peer' => 'p1', 'sdp' => 'x']); $this->assertSame(['type' => 'error', 'peer' => 'p1', 'error' => 'boom'], $result); } }