onOpen($a); $this->assertSame(['type' => 'welcome', 'peer' => 'a'], $a->lastJson()); } public function test_join_returns_existing_peers_and_notifies_them(): void { $handler = new RelaySignalingHandler; $a = new FakePeer('a'); $b = new FakePeer('b'); $handler->onOpen($a); $handler->onOpen($b); $handler->onMessage($a, $this->json(['type' => 'join', 'room' => 'lobby'])); $handler->onMessage($b, $this->json(['type' => 'join', 'room' => 'lobby'])); // b learns 'a' is already there $this->assertSame(['type' => 'joined', 'room' => 'lobby', 'peers' => ['a']], $b->lastJson()); // a is told b joined $this->assertContains(['type' => 'peer-joined', 'room' => 'lobby', 'peer' => 'b'], $a->jsonMessages()); } public function test_relay_forwards_the_payload_to_the_target_with_the_sender(): void { $handler = new RelaySignalingHandler; $a = new FakePeer('a'); $b = new FakePeer('b'); $handler->onOpen($a); $handler->onOpen($b); $handler->onMessage($a, $this->json(['type' => 'relay', 'to' => 'b', 'data' => ['sdp' => 'OFFER']])); $this->assertSame(['type' => 'relay', 'from' => 'a', 'data' => ['sdp' => 'OFFER']], $b->lastJson()); } public function test_relay_to_an_unknown_peer_errors_the_sender(): void { $handler = new RelaySignalingHandler; $a = new FakePeer('a'); $handler->onOpen($a); $handler->onMessage($a, $this->json(['type' => 'relay', 'to' => 'ghost', 'data' => []])); $this->assertSame('error', $a->lastJson()['type']); } public function test_leave_notifies_the_remaining_peers(): void { $handler = new RelaySignalingHandler; $a = new FakePeer('a'); $b = new FakePeer('b'); $handler->onOpen($a); $handler->onOpen($b); $handler->onMessage($a, $this->json(['type' => 'join', 'room' => 'lobby'])); $handler->onMessage($b, $this->json(['type' => 'join', 'room' => 'lobby'])); $handler->onMessage($b, $this->json(['type' => 'leave'])); $this->assertContains(['type' => 'peer-left', 'peer' => 'b'], $a->jsonMessages()); $this->assertSame(['type' => 'left'], $b->lastJson()); } public function test_disconnect_leaves_the_room_and_forgets_the_peer(): void { $handler = new RelaySignalingHandler; $a = new FakePeer('a'); $b = new FakePeer('b'); $handler->onOpen($a); $handler->onOpen($b); $handler->onMessage($a, $this->json(['type' => 'join', 'room' => 'lobby'])); $handler->onMessage($b, $this->json(['type' => 'join', 'room' => 'lobby'])); $handler->onClose($b); $this->assertContains(['type' => 'peer-left', 'peer' => 'b'], $a->jsonMessages()); $this->assertFalse($handler->peers()->has('b')); } public function test_broadcast_reaches_the_rest_of_the_room_only(): void { $handler = new RelaySignalingHandler; $peers = [new FakePeer('a'), new FakePeer('b'), new FakePeer('c')]; foreach ($peers as $p) { $handler->onOpen($p); $handler->onMessage($p, $this->json(['type' => 'join', 'room' => 'r'])); } [$a, $b, $c] = $peers; $handler->onMessage($a, $this->json(['type' => 'broadcast', 'data' => ['x' => 1]])); $expected = ['type' => 'broadcast', 'from' => 'a', 'data' => ['x' => 1]]; $this->assertContains($expected, $b->jsonMessages()); $this->assertContains($expected, $c->jsonMessages()); $this->assertNotContains($expected, $a->jsonMessages()); } public function test_invalid_json_and_unknown_type_are_errors(): void { $handler = new RelaySignalingHandler; $a = new FakePeer('a'); $handler->onOpen($a); $handler->onMessage($a, 'not json'); $this->assertSame('error', $a->lastJson()['type']); $handler->onMessage($a, $this->json(['type' => 'wat'])); $this->assertSame('error', $a->lastJson()['type']); } /** @param array $data */ private function json(array $data): string { return (string) json_encode($data); } }