ClientTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. <?php
  2. /**
  3. * Test case for Client.
  4. * Note that this test is performed by mocking socket/stream calls.
  5. */
  6. declare(strict_types=1);
  7. namespace WebSocket;
  8. use PHPUnit\Framework\TestCase;
  9. class ClientTest extends TestCase
  10. {
  11. public function setUp(): void
  12. {
  13. error_reporting(-1);
  14. }
  15. public function testClientMasked(): void
  16. {
  17. MockSocket::initialize('client.connect', $this);
  18. $client = new Client('ws://localhost:8000/my/mock/path');
  19. $client->send('Connect');
  20. $this->assertTrue(MockSocket::isEmpty());
  21. $this->assertEquals(4096, $client->getFragmentSize());
  22. MockSocket::initialize('send-receive', $this);
  23. $client->send('Sending a message');
  24. $message = $client->receive();
  25. $this->assertTrue(MockSocket::isEmpty());
  26. $this->assertEquals('text', $client->getLastOpcode());
  27. MockSocket::initialize('client.close', $this);
  28. $this->assertTrue($client->isConnected());
  29. $this->assertNull($client->getCloseStatus());
  30. $client->close();
  31. $this->assertFalse($client->isConnected());
  32. $this->assertEquals(1000, $client->getCloseStatus());
  33. $this->assertEquals('close', $client->getLastOpcode());
  34. $client->close();
  35. $this->assertFalse($client->isConnected());
  36. $this->assertEquals(1000, $client->getCloseStatus());
  37. $this->assertEquals('close', $client->getLastOpcode());
  38. $this->assertTrue(MockSocket::isEmpty());
  39. }
  40. public function testDestruct(): void
  41. {
  42. MockSocket::initialize('client.connect', $this);
  43. $client = new Client('ws://localhost:8000/my/mock/path');
  44. $client->send('Connect');
  45. $this->assertTrue(MockSocket::isEmpty());
  46. MockSocket::initialize('client.destruct', $this);
  47. }
  48. public function testClienExtendedUrl(): void
  49. {
  50. MockSocket::initialize('client.connect-extended', $this);
  51. $client = new Client('ws://localhost:8000/my/mock/path?my_query=yes#my_fragment');
  52. $client->send('Connect');
  53. $this->assertTrue(MockSocket::isEmpty());
  54. }
  55. public function testClientWithTimeout(): void
  56. {
  57. MockSocket::initialize('client.connect-timeout', $this);
  58. $client = new Client('ws://localhost:8000/my/mock/path', ['timeout' => 300]);
  59. $client->send('Connect');
  60. $this->assertTrue(MockSocket::isEmpty());
  61. }
  62. public function testClientWithContext(): void
  63. {
  64. MockSocket::initialize('client.connect-context', $this);
  65. $client = new Client('ws://localhost:8000/my/mock/path', ['context' => '@mock-stream-context']);
  66. $client->send('Connect');
  67. $this->assertTrue(MockSocket::isEmpty());
  68. }
  69. public function testClientAuthed(): void
  70. {
  71. MockSocket::initialize('client.connect-authed', $this);
  72. $client = new Client('wss://usename:password@localhost:8000/my/mock/path');
  73. $client->send('Connect');
  74. $this->assertTrue(MockSocket::isEmpty());
  75. }
  76. public function testWithHeaders(): void
  77. {
  78. MockSocket::initialize('client.connect-headers', $this);
  79. $client = new Client('ws://localhost:8000/my/mock/path', [
  80. 'origin' => 'Origin header',
  81. 'headers' => ['Generic header' => 'Generic content'],
  82. ]);
  83. $client->send('Connect');
  84. $this->assertTrue(MockSocket::isEmpty());
  85. }
  86. public function testPayload128(): void
  87. {
  88. MockSocket::initialize('client.connect', $this);
  89. $client = new Client('ws://localhost:8000/my/mock/path');
  90. $client->send('Connect');
  91. $this->assertTrue(MockSocket::isEmpty());
  92. $payload = file_get_contents(__DIR__ . '/mock/payload.128.txt');
  93. MockSocket::initialize('send-receive-128', $this);
  94. $client->send($payload, 'text', false);
  95. $message = $client->receive();
  96. $this->assertEquals($payload, $message);
  97. $this->assertTrue(MockSocket::isEmpty());
  98. }
  99. public function testPayload65536(): void
  100. {
  101. MockSocket::initialize('client.connect', $this);
  102. $client = new Client('ws://localhost:8000/my/mock/path');
  103. $client->send('Connect');
  104. $this->assertTrue(MockSocket::isEmpty());
  105. $payload = file_get_contents(__DIR__ . '/mock/payload.65536.txt');
  106. $client->setFragmentSize(65540);
  107. MockSocket::initialize('send-receive-65536', $this);
  108. $client->send($payload, 'text', false);
  109. $message = $client->receive();
  110. $this->assertEquals($payload, $message);
  111. $this->assertTrue(MockSocket::isEmpty());
  112. $this->assertEquals(65540, $client->getFragmentSize());
  113. }
  114. public function testMultiFragment(): void
  115. {
  116. MockSocket::initialize('client.connect', $this);
  117. $client = new Client('ws://localhost:8000/my/mock/path');
  118. $client->send('Connect');
  119. $this->assertTrue(MockSocket::isEmpty());
  120. MockSocket::initialize('send-receive-multi-fragment', $this);
  121. $client->setFragmentSize(8);
  122. $client->send('Multi fragment test');
  123. $message = $client->receive();
  124. $this->assertEquals('Multi fragment test', $message);
  125. $this->assertTrue(MockSocket::isEmpty());
  126. $this->assertEquals(8, $client->getFragmentSize());
  127. }
  128. public function testPingPong(): void
  129. {
  130. MockSocket::initialize('client.connect', $this);
  131. $client = new Client('ws://localhost:8000/my/mock/path');
  132. $client->send('Connect');
  133. $this->assertTrue(MockSocket::isEmpty());
  134. MockSocket::initialize('ping-pong', $this);
  135. $client->send('Server ping', 'ping');
  136. $client->send('', 'ping');
  137. $message = $client->receive();
  138. $this->assertEquals('Receiving a message', $message);
  139. $this->assertEquals('text', $client->getLastOpcode());
  140. $this->assertTrue(MockSocket::isEmpty());
  141. }
  142. public function testRemoteClose(): void
  143. {
  144. MockSocket::initialize('client.connect', $this);
  145. $client = new Client('ws://localhost:8000/my/mock/path');
  146. $client->send('Connect');
  147. $this->assertTrue(MockSocket::isEmpty());
  148. MockSocket::initialize('close-remote', $this);
  149. $message = $client->receive();
  150. $this->assertEquals('', $message);
  151. $this->assertFalse($client->isConnected());
  152. $this->assertEquals(17260, $client->getCloseStatus());
  153. $this->assertEquals('close', $client->getLastOpcode());
  154. $this->assertTrue(MockSocket::isEmpty());
  155. }
  156. public function testSetTimeout(): void
  157. {
  158. MockSocket::initialize('client.connect', $this);
  159. $client = new Client('ws://localhost:8000/my/mock/path');
  160. $client->send('Connect');
  161. $this->assertTrue(MockSocket::isEmpty());
  162. MockSocket::initialize('config-timeout', $this);
  163. $client->setTimeout(300);
  164. $this->assertTrue($client->isConnected());
  165. $this->assertTrue(MockSocket::isEmpty());
  166. }
  167. public function testReconnect(): void
  168. {
  169. MockSocket::initialize('client.connect', $this);
  170. $client = new Client('ws://localhost:8000/my/mock/path');
  171. $client->send('Connect');
  172. $this->assertTrue(MockSocket::isEmpty());
  173. MockSocket::initialize('client.close', $this);
  174. $this->assertTrue($client->isConnected());
  175. $this->assertNull($client->getCloseStatus());
  176. $client->close();
  177. $this->assertFalse($client->isConnected());
  178. $this->assertEquals(1000, $client->getCloseStatus());
  179. $this->assertEquals('close', $client->getLastOpcode());
  180. $this->assertTrue(MockSocket::isEmpty());
  181. MockSocket::initialize('client.reconnect', $this);
  182. $message = $client->receive();
  183. $this->assertTrue($client->isConnected());
  184. $this->assertTrue(MockSocket::isEmpty());
  185. }
  186. public function testPersistentConnection(): void
  187. {
  188. MockSocket::initialize('client.connect-persistent', $this);
  189. $client = new Client('ws://localhost:8000/my/mock/path', ['persistent' => true]);
  190. $client->send('Connect');
  191. $this->assertTrue(MockSocket::isEmpty());
  192. }
  193. public function testBadScheme(): void
  194. {
  195. MockSocket::initialize('client.connect', $this);
  196. $client = new Client('bad://localhost:8000/my/mock/path');
  197. $this->expectException('WebSocket\BadUriException');
  198. $this->expectExceptionMessage('Url should have scheme ws or wss');
  199. $client->send('Connect');
  200. }
  201. public function testBadUrl(): void
  202. {
  203. MockSocket::initialize('client.connect', $this);
  204. $client = new Client('this is not an url');
  205. $this->expectException('WebSocket\BadUriException');
  206. $this->expectExceptionMessage('Invalid url \'this is not an url\' provided.');
  207. $client->send('Connect');
  208. }
  209. public function testBadStreamContext(): void
  210. {
  211. MockSocket::initialize('client.connect-bad-context', $this);
  212. $client = new Client('ws://localhost:8000/my/mock/path', ['context' => 'BAD']);
  213. $this->expectException('InvalidArgumentException');
  214. $this->expectExceptionMessage('Stream context in $options[\'context\'] isn\'t a valid context');
  215. $client->send('Connect');
  216. }
  217. public function testFailedConnection(): void
  218. {
  219. MockSocket::initialize('client.connect-failed', $this);
  220. $client = new Client('ws://localhost:8000/my/mock/path');
  221. $this->expectException('WebSocket\ConnectionException');
  222. $this->expectExceptionCode(0);
  223. $this->expectExceptionMessage('Could not open socket to "localhost:8000"');
  224. $client->send('Connect');
  225. }
  226. public function testInvalidUpgrade(): void
  227. {
  228. MockSocket::initialize('client.connect-invalid-upgrade', $this);
  229. $client = new Client('ws://localhost:8000/my/mock/path');
  230. $this->expectException('WebSocket\ConnectionException');
  231. $this->expectExceptionCode(0);
  232. $this->expectExceptionMessage('Connection to \'ws://localhost/my/mock/path\' failed');
  233. $client->send('Connect');
  234. }
  235. public function testInvalidKey(): void
  236. {
  237. MockSocket::initialize('client.connect-invalid-key', $this);
  238. $client = new Client('ws://localhost:8000/my/mock/path');
  239. $this->expectException('WebSocket\ConnectionException');
  240. $this->expectExceptionCode(0);
  241. $this->expectExceptionMessage('Server sent bad upgrade response');
  242. $client->send('Connect');
  243. }
  244. public function testSendBadOpcode(): void
  245. {
  246. MockSocket::initialize('client.connect', $this);
  247. $client = new Client('ws://localhost:8000/my/mock/path');
  248. $client->send('Connect');
  249. MockSocket::initialize('send-bad-opcode', $this);
  250. $this->expectException('WebSocket\BadOpcodeException');
  251. $this->expectExceptionMessage('Bad opcode \'bad\'. Try \'text\' or \'binary\'.');
  252. $client->send('Bad Opcode', 'bad');
  253. }
  254. public function testRecieveBadOpcode(): void
  255. {
  256. MockSocket::initialize('client.connect', $this);
  257. $client = new Client('ws://localhost:8000/my/mock/path');
  258. $client->send('Connect');
  259. MockSocket::initialize('receive-bad-opcode', $this);
  260. $this->expectException('WebSocket\ConnectionException');
  261. $this->expectExceptionCode(1026);
  262. $this->expectExceptionMessage('Bad opcode in websocket frame: 12');
  263. $message = $client->receive();
  264. }
  265. public function testBrokenWrite(): void
  266. {
  267. MockSocket::initialize('client.connect', $this);
  268. $client = new Client('ws://localhost:8000/my/mock/path');
  269. $client->send('Connect');
  270. MockSocket::initialize('send-broken-write', $this);
  271. $this->expectException('WebSocket\ConnectionException');
  272. $this->expectExceptionCode(1025);
  273. $this->expectExceptionMessage('Could only write 18 out of 22 bytes.');
  274. $client->send('Failing to write');
  275. }
  276. public function testFailedWrite(): void
  277. {
  278. MockSocket::initialize('client.connect', $this);
  279. $client = new Client('ws://localhost:8000/my/mock/path');
  280. $client->send('Connect');
  281. MockSocket::initialize('send-failed-write', $this);
  282. $this->expectException('WebSocket\TimeoutException');
  283. $this->expectExceptionCode(1024);
  284. $this->expectExceptionMessage('Failed to write 22 bytes.');
  285. $client->send('Failing to write');
  286. }
  287. public function testBrokenRead(): void
  288. {
  289. MockSocket::initialize('client.connect', $this);
  290. $client = new Client('ws://localhost:8000/my/mock/path');
  291. $client->send('Connect');
  292. MockSocket::initialize('receive-broken-read', $this);
  293. $this->expectException('WebSocket\ConnectionException');
  294. $this->expectExceptionCode(1025);
  295. $this->expectExceptionMessage('Broken frame, read 0 of stated 2 bytes.');
  296. $client->receive();
  297. }
  298. public function testEmptyRead(): void
  299. {
  300. MockSocket::initialize('client.connect', $this);
  301. $client = new Client('ws://localhost:8000/my/mock/path');
  302. $client->send('Connect');
  303. MockSocket::initialize('receive-empty-read', $this);
  304. $this->expectException('WebSocket\TimeoutException');
  305. $this->expectExceptionCode(1024);
  306. $this->expectExceptionMessage('Empty read; connection dead?');
  307. $client->receive();
  308. }
  309. }