1<?php 2 3namespace Sabre\HTTP; 4 5class ClientTest extends \PHPUnit_Framework_TestCase { 6 7 protected $client; 8 9 function testCreateCurlSettingsArrayGET() { 10 11 $client = new ClientMock(); 12 $client->addCurlSetting(CURLOPT_POSTREDIR, 0); 13 14 $request = new Request('GET', 'http://example.org/', ['X-Foo' => 'bar']); 15 16 $settings = [ 17 CURLOPT_RETURNTRANSFER => true, 18 CURLOPT_HEADER => true, 19 CURLOPT_POSTREDIR => 0, 20 CURLOPT_HTTPHEADER => ['X-Foo: bar'], 21 CURLOPT_NOBODY => false, 22 CURLOPT_URL => 'http://example.org/', 23 CURLOPT_CUSTOMREQUEST => 'GET', 24 CURLOPT_POSTFIELDS => null, 25 CURLOPT_PUT => false, 26 ]; 27 28 // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM 29 // at least if this unit test fails in the future we know it is :) 30 if (defined('HHVM_VERSION') === false) { 31 $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; 32 $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; 33 } 34 35 36 $this->assertEquals($settings, $client->createCurlSettingsArray($request)); 37 38 } 39 40 function testCreateCurlSettingsArrayHEAD() { 41 42 $client = new ClientMock(); 43 $request = new Request('HEAD', 'http://example.org/', ['X-Foo' => 'bar']); 44 45 46 $settings = [ 47 CURLOPT_RETURNTRANSFER => true, 48 CURLOPT_HEADER => true, 49 CURLOPT_NOBODY => true, 50 CURLOPT_CUSTOMREQUEST => 'HEAD', 51 CURLOPT_HTTPHEADER => ['X-Foo: bar'], 52 CURLOPT_URL => 'http://example.org/', 53 CURLOPT_POSTFIELDS => '', 54 CURLOPT_PUT => false, 55 ]; 56 57 // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM 58 // at least if this unit test fails in the future we know it is :) 59 if (defined('HHVM_VERSION') === false) { 60 $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; 61 $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; 62 } 63 64 $this->assertEquals($settings, $client->createCurlSettingsArray($request)); 65 66 } 67 68 function testCreateCurlSettingsArrayGETAfterHEAD() { 69 70 $client = new ClientMock(); 71 $request = new Request('HEAD', 'http://example.org/', ['X-Foo' => 'bar']); 72 73 // Parsing the settings for this method, and discarding the result. 74 // This will cause the client to automatically persist previous 75 // settings and will help us detect problems. 76 $client->createCurlSettingsArray($request); 77 78 // This is the real request. 79 $request = new Request('GET', 'http://example.org/', ['X-Foo' => 'bar']); 80 81 $settings = [ 82 CURLOPT_CUSTOMREQUEST => 'GET', 83 CURLOPT_RETURNTRANSFER => true, 84 CURLOPT_HEADER => true, 85 CURLOPT_HTTPHEADER => ['X-Foo: bar'], 86 CURLOPT_NOBODY => false, 87 CURLOPT_URL => 'http://example.org/', 88 CURLOPT_POSTFIELDS => '', 89 CURLOPT_PUT => false, 90 ]; 91 92 // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM 93 // at least if this unit test fails in the future we know it is :) 94 if (defined('HHVM_VERSION') === false) { 95 $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; 96 $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; 97 } 98 99 $this->assertEquals($settings, $client->createCurlSettingsArray($request)); 100 101 } 102 103 function testCreateCurlSettingsArrayPUTStream() { 104 105 $client = new ClientMock(); 106 107 $h = fopen('php://memory', 'r+'); 108 fwrite($h, 'booh'); 109 $request = new Request('PUT', 'http://example.org/', ['X-Foo' => 'bar'], $h); 110 111 $settings = [ 112 CURLOPT_RETURNTRANSFER => true, 113 CURLOPT_HEADER => true, 114 CURLOPT_PUT => true, 115 CURLOPT_INFILE => $h, 116 CURLOPT_NOBODY => false, 117 CURLOPT_CUSTOMREQUEST => 'PUT', 118 CURLOPT_HTTPHEADER => ['X-Foo: bar'], 119 CURLOPT_URL => 'http://example.org/', 120 ]; 121 122 // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM 123 // at least if this unit test fails in the future we know it is :) 124 if (defined('HHVM_VERSION') === false) { 125 $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; 126 $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; 127 } 128 129 $this->assertEquals($settings, $client->createCurlSettingsArray($request)); 130 131 } 132 133 function testCreateCurlSettingsArrayPUTString() { 134 135 $client = new ClientMock(); 136 $request = new Request('PUT', 'http://example.org/', ['X-Foo' => 'bar'], 'boo'); 137 138 $settings = [ 139 CURLOPT_RETURNTRANSFER => true, 140 CURLOPT_HEADER => true, 141 CURLOPT_NOBODY => false, 142 CURLOPT_POSTFIELDS => 'boo', 143 CURLOPT_CUSTOMREQUEST => 'PUT', 144 CURLOPT_HTTPHEADER => ['X-Foo: bar'], 145 CURLOPT_URL => 'http://example.org/', 146 ]; 147 148 // FIXME: CURLOPT_PROTOCOLS and CURLOPT_REDIR_PROTOCOLS are currently unsupported by HHVM 149 // at least if this unit test fails in the future we know it is :) 150 if (defined('HHVM_VERSION') === false) { 151 $settings[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; 152 $settings[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; 153 } 154 155 $this->assertEquals($settings, $client->createCurlSettingsArray($request)); 156 157 } 158 159 function testSend() { 160 161 $client = new ClientMock(); 162 $request = new Request('GET', 'http://example.org/'); 163 164 $client->on('doRequest', function($request, &$response) { 165 $response = new Response(200); 166 }); 167 168 $response = $client->send($request); 169 170 $this->assertEquals(200, $response->getStatus()); 171 172 } 173 174 function testSendClientError() { 175 176 $client = new ClientMock(); 177 $request = new Request('GET', 'http://example.org/'); 178 179 $client->on('doRequest', function($request, &$response) { 180 throw new ClientException('aaah', 1); 181 }); 182 $called = false; 183 $client->on('exception', function() use (&$called) { 184 $called = true; 185 }); 186 187 try { 188 $client->send($request); 189 $this->fail('send() should have thrown an exception'); 190 } catch (ClientException $e) { 191 192 } 193 $this->assertTrue($called); 194 195 } 196 197 function testSendHttpError() { 198 199 $client = new ClientMock(); 200 $request = new Request('GET', 'http://example.org/'); 201 202 $client->on('doRequest', function($request, &$response) { 203 $response = new Response(404); 204 }); 205 $called = 0; 206 $client->on('error', function() use (&$called) { 207 $called++; 208 }); 209 $client->on('error:404', function() use (&$called) { 210 $called++; 211 }); 212 213 $client->send($request); 214 $this->assertEquals(2, $called); 215 216 } 217 218 function testSendRetry() { 219 220 $client = new ClientMock(); 221 $request = new Request('GET', 'http://example.org/'); 222 223 $called = 0; 224 $client->on('doRequest', function($request, &$response) use (&$called) { 225 $called++; 226 if ($called < 3) { 227 $response = new Response(404); 228 } else { 229 $response = new Response(200); 230 } 231 }); 232 233 $errorCalled = 0; 234 $client->on('error', function($request, $response, &$retry, $retryCount) use (&$errorCalled) { 235 236 $errorCalled++; 237 $retry = true; 238 239 }); 240 241 $response = $client->send($request); 242 $this->assertEquals(3, $called); 243 $this->assertEquals(2, $errorCalled); 244 $this->assertEquals(200, $response->getStatus()); 245 246 } 247 248 function testHttpErrorException() { 249 250 $client = new ClientMock(); 251 $client->setThrowExceptions(true); 252 $request = new Request('GET', 'http://example.org/'); 253 254 $client->on('doRequest', function($request, &$response) { 255 $response = new Response(404); 256 }); 257 258 try { 259 $client->send($request); 260 $this->fail('An exception should have been thrown'); 261 } catch (ClientHttpException $e) { 262 $this->assertEquals(404, $e->getHttpStatus()); 263 $this->assertInstanceOf('Sabre\HTTP\Response', $e->getResponse()); 264 } 265 266 } 267 268 function testParseCurlResult() { 269 270 $client = new ClientMock(); 271 $client->on('curlStuff', function(&$return) { 272 273 $return = [ 274 [ 275 'header_size' => 33, 276 'http_code' => 200, 277 ], 278 0, 279 '', 280 ]; 281 282 }); 283 284 $body = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; 285 $result = $client->parseCurlResult($body, 'foobar'); 286 287 $this->assertEquals(Client::STATUS_SUCCESS, $result['status']); 288 $this->assertEquals(200, $result['http_code']); 289 $this->assertEquals(200, $result['response']->getStatus()); 290 $this->assertEquals(['Header1' => ['Val1']], $result['response']->getHeaders()); 291 $this->assertEquals('Foo', $result['response']->getBodyAsString()); 292 293 } 294 295 function testParseCurlError() { 296 297 $client = new ClientMock(); 298 $client->on('curlStuff', function(&$return) { 299 300 $return = [ 301 [], 302 1, 303 'Curl error', 304 ]; 305 306 }); 307 308 $body = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; 309 $result = $client->parseCurlResult($body, 'foobar'); 310 311 $this->assertEquals(Client::STATUS_CURLERROR, $result['status']); 312 $this->assertEquals(1, $result['curl_errno']); 313 $this->assertEquals('Curl error', $result['curl_errmsg']); 314 315 } 316 317 function testDoRequest() { 318 319 $client = new ClientMock(); 320 $request = new Request('GET', 'http://example.org/'); 321 $client->on('curlExec', function(&$return) { 322 323 $return = "HTTP/1.1 200 OK\r\nHeader1:Val1\r\n\r\nFoo"; 324 325 }); 326 $client->on('curlStuff', function(&$return) { 327 328 $return = [ 329 [ 330 'header_size' => 33, 331 'http_code' => 200, 332 ], 333 0, 334 '', 335 ]; 336 337 }); 338 $response = $client->doRequest($request); 339 $this->assertEquals(200, $response->getStatus()); 340 $this->assertEquals(['Header1' => ['Val1']], $response->getHeaders()); 341 $this->assertEquals('Foo', $response->getBodyAsString()); 342 343 } 344 345 function testDoRequestCurlError() { 346 347 $client = new ClientMock(); 348 $request = new Request('GET', 'http://example.org/'); 349 $client->on('curlExec', function(&$return) { 350 351 $return = ""; 352 353 }); 354 $client->on('curlStuff', function(&$return) { 355 356 $return = [ 357 [], 358 1, 359 'Curl error', 360 ]; 361 362 }); 363 364 try { 365 $response = $client->doRequest($request); 366 $this->fail('This should have thrown an exception'); 367 } catch (ClientException $e) { 368 $this->assertEquals(1, $e->getCode()); 369 $this->assertEquals('Curl error', $e->getMessage()); 370 } 371 372 } 373 374} 375 376class ClientMock extends Client { 377 378 protected $persistedSettings = []; 379 380 /** 381 * Making this method public. 382 * 383 * We are also going to persist all settings this method generates. While 384 * the underlying object doesn't behave exactly the same, it helps us 385 * simulate what curl does internally, and helps us identify problems with 386 * settings that are set by _some_ methods and not correctly reset by other 387 * methods after subsequent use. 388 * forces 389 */ 390 function createCurlSettingsArray(RequestInterface $request) { 391 392 $settings = parent::createCurlSettingsArray($request); 393 $settings = $settings + $this->persistedSettings; 394 $this->persistedSettings = $settings; 395 return $settings; 396 397 } 398 /** 399 * Making this method public. 400 */ 401 function parseCurlResult($response, $curlHandle) { 402 403 return parent::parseCurlResult($response, $curlHandle); 404 405 } 406 407 /** 408 * This method is responsible for performing a single request. 409 * 410 * @param RequestInterface $request 411 * @return ResponseInterface 412 */ 413 function doRequest(RequestInterface $request) { 414 415 $response = null; 416 $this->emit('doRequest', [$request, &$response]); 417 418 // If nothing modified $response, we're using the default behavior. 419 if (is_null($response)) { 420 return parent::doRequest($request); 421 } else { 422 return $response; 423 } 424 425 } 426 427 /** 428 * Returns a bunch of information about a curl request. 429 * 430 * This method exists so it can easily be overridden and mocked. 431 * 432 * @param resource $curlHandle 433 * @return array 434 */ 435 protected function curlStuff($curlHandle) { 436 437 $return = null; 438 $this->emit('curlStuff', [&$return]); 439 440 // If nothing modified $return, we're using the default behavior. 441 if (is_null($return)) { 442 return parent::curlStuff($curlHandle); 443 } else { 444 return $return; 445 } 446 447 } 448 449 /** 450 * Calls curl_exec 451 * 452 * This method exists so it can easily be overridden and mocked. 453 * 454 * @param resource $curlHandle 455 * @return string 456 */ 457 protected function curlExec($curlHandle) { 458 459 $return = null; 460 $this->emit('curlExec', [&$return]); 461 462 // If nothing modified $return, we're using the default behavior. 463 if (is_null($return)) { 464 return parent::curlExec($curlHandle); 465 } else { 466 return $return; 467 } 468 469 } 470 471} 472