ClientTest.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * Redistributions of files must retain the above copyright notice.
  8. *
  9. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  10. * @link http://cakephp.org CakePHP(tm) Project
  11. * @since 3.0.0
  12. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  13. */
  14. namespace Cake\Test\TestCase\Http;
  15. use Cake\Core\Configure;
  16. use Cake\Http\Client;
  17. use Cake\Http\Client\Request;
  18. use Cake\Http\Client\Response;
  19. use Cake\Http\Cookie\Cookie;
  20. use Cake\Http\Cookie\CookieCollection;
  21. use Cake\TestSuite\TestCase;
  22. /**
  23. * HTTP client test.
  24. */
  25. class ClientTest extends TestCase
  26. {
  27. /**
  28. * Test storing config options and modifying them.
  29. *
  30. * @return void
  31. */
  32. public function testConstructConfig()
  33. {
  34. $config = [
  35. 'scheme' => 'http',
  36. 'host' => 'example.org',
  37. ];
  38. $http = new Client($config);
  39. $result = $http->config();
  40. foreach ($config as $key => $val) {
  41. $this->assertEquals($val, $result[$key]);
  42. }
  43. $result = $http->config([
  44. 'auth' => ['username' => 'mark', 'password' => 'secret']
  45. ]);
  46. $this->assertSame($result, $http);
  47. $result = $http->config();
  48. $expected = [
  49. 'scheme' => 'http',
  50. 'host' => 'example.org',
  51. 'auth' => ['username' => 'mark', 'password' => 'secret']
  52. ];
  53. foreach ($expected as $key => $val) {
  54. $this->assertEquals($val, $result[$key]);
  55. }
  56. }
  57. /**
  58. * Data provider for buildUrl() tests
  59. *
  60. * @return array
  61. */
  62. public static function urlProvider()
  63. {
  64. return [
  65. [
  66. 'http://example.com/test.html',
  67. 'http://example.com/test.html',
  68. [],
  69. null,
  70. 'Null options'
  71. ],
  72. [
  73. 'http://example.com/test.html',
  74. 'http://example.com/test.html',
  75. [],
  76. [],
  77. 'Simple string'
  78. ],
  79. [
  80. 'http://example.com/test.html',
  81. '/test.html',
  82. [],
  83. ['host' => 'example.com'],
  84. 'host name option',
  85. ],
  86. [
  87. 'https://example.com/test.html',
  88. '/test.html',
  89. [],
  90. ['host' => 'example.com', 'scheme' => 'https'],
  91. 'HTTPS',
  92. ],
  93. [
  94. 'http://example.com:8080/test.html',
  95. '/test.html',
  96. [],
  97. ['host' => 'example.com', 'port' => '8080'],
  98. 'Non standard port',
  99. ],
  100. [
  101. 'http://example.com/test.html',
  102. '/test.html',
  103. [],
  104. ['host' => 'example.com', 'port' => '80'],
  105. 'standard port, does not display'
  106. ],
  107. [
  108. 'https://example.com/test.html',
  109. '/test.html',
  110. [],
  111. ['host' => 'example.com', 'scheme' => 'https', 'port' => '443'],
  112. 'standard port, does not display'
  113. ],
  114. [
  115. 'http://example.com/test.html',
  116. 'http://example.com/test.html',
  117. [],
  118. ['host' => 'example.com', 'scheme' => 'https'],
  119. 'options do not duplicate'
  120. ],
  121. [
  122. 'http://example.com/search?q=hi+there&cat%5Bid%5D%5B0%5D=2&cat%5Bid%5D%5B1%5D=3',
  123. 'http://example.com/search',
  124. ['q' => 'hi there', 'cat' => ['id' => [2, 3]]],
  125. [],
  126. 'query string data.'
  127. ],
  128. [
  129. 'http://example.com/search?q=hi+there&id=12',
  130. 'http://example.com/search?q=hi+there',
  131. ['id' => '12'],
  132. [],
  133. 'query string data with some already on the url.'
  134. ],
  135. ];
  136. }
  137. /**
  138. * @dataProvider urlProvider
  139. */
  140. public function testBuildUrl($expected, $url, $query, $opts)
  141. {
  142. $http = new Client();
  143. $result = $http->buildUrl($url, $query, $opts);
  144. $this->assertEquals($expected, $result);
  145. }
  146. /**
  147. * test simple get request with headers & cookies.
  148. *
  149. * @return void
  150. */
  151. public function testGetSimpleWithHeadersAndCookies()
  152. {
  153. $response = new Response();
  154. $headers = [
  155. 'User-Agent' => 'Cake',
  156. 'Connection' => 'close',
  157. 'Content-Type' => 'application/x-www-form-urlencoded',
  158. ];
  159. $cookies = [
  160. 'split' => 'value'
  161. ];
  162. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  163. ->setMethods(['send'])
  164. ->getMock();
  165. $mock->expects($this->once())
  166. ->method('send')
  167. ->with($this->callback(function ($request) use ($cookies, $headers) {
  168. $this->assertInstanceOf('Cake\Http\Client\Request', $request);
  169. $this->assertEquals(Request::METHOD_GET, $request->getMethod());
  170. $this->assertEquals('http://cakephp.org/test.html', $request->getUri() . '');
  171. $this->assertEquals('split=value', $request->getHeaderLine('Cookie'));
  172. $this->assertEquals($headers['Content-Type'], $request->getHeaderLine('content-type'));
  173. $this->assertEquals($headers['Connection'], $request->getHeaderLine('connection'));
  174. return true;
  175. }))
  176. ->will($this->returnValue([$response]));
  177. $http = new Client(['adapter' => $mock]);
  178. $result = $http->get('http://cakephp.org/test.html', [], [
  179. 'headers' => $headers,
  180. 'cookies' => $cookies,
  181. ]);
  182. $this->assertSame($result, $response);
  183. }
  184. /**
  185. * test get request with no data
  186. *
  187. * @return void
  188. */
  189. public function testGetNoData()
  190. {
  191. $response = new Response();
  192. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  193. ->setMethods(['send'])
  194. ->getMock();
  195. $mock->expects($this->once())
  196. ->method('send')
  197. ->with($this->callback(function ($request) {
  198. $this->assertEquals(Request::METHOD_GET, $request->getMethod());
  199. $this->assertEmpty($request->getHeaderLine('Content-Type'), 'Should have no content-type set');
  200. $this->assertEquals(
  201. 'http://cakephp.org/search',
  202. $request->getUri() . ''
  203. );
  204. return true;
  205. }))
  206. ->will($this->returnValue([$response]));
  207. $http = new Client([
  208. 'host' => 'cakephp.org',
  209. 'adapter' => $mock
  210. ]);
  211. $result = $http->get('/search');
  212. $this->assertSame($result, $response);
  213. }
  214. /**
  215. * test get request with querystring data
  216. *
  217. * @return void
  218. */
  219. public function testGetQuerystring()
  220. {
  221. $response = new Response();
  222. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  223. ->setMethods(['send'])
  224. ->getMock();
  225. $mock->expects($this->once())
  226. ->method('send')
  227. ->with($this->callback(function ($request) {
  228. $this->assertEquals(Request::METHOD_GET, $request->getMethod());
  229. $this->assertEquals(
  230. 'http://cakephp.org/search?q=hi+there&Category%5Bid%5D%5B0%5D=2&Category%5Bid%5D%5B1%5D=3',
  231. $request->getUri() . ''
  232. );
  233. return true;
  234. }))
  235. ->will($this->returnValue([$response]));
  236. $http = new Client([
  237. 'host' => 'cakephp.org',
  238. 'adapter' => $mock
  239. ]);
  240. $result = $http->get('/search', [
  241. 'q' => 'hi there',
  242. 'Category' => ['id' => [2, 3]]
  243. ]);
  244. $this->assertSame($result, $response);
  245. }
  246. /**
  247. * test get request with string of query data.
  248. *
  249. * @return void
  250. */
  251. public function testGetQuerystringString()
  252. {
  253. $response = new Response();
  254. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  255. ->setMethods(['send'])
  256. ->getMock();
  257. $mock->expects($this->once())
  258. ->method('send')
  259. ->with($this->callback(function ($request) {
  260. $this->assertEquals(
  261. 'http://cakephp.org/search?q=hi+there&Category%5Bid%5D%5B0%5D=2&Category%5Bid%5D%5B1%5D=3',
  262. $request->getUri() . ''
  263. );
  264. return true;
  265. }))
  266. ->will($this->returnValue([$response]));
  267. $http = new Client([
  268. 'host' => 'cakephp.org',
  269. 'adapter' => $mock
  270. ]);
  271. $data = [
  272. 'q' => 'hi there',
  273. 'Category' => ['id' => [2, 3]]
  274. ];
  275. $result = $http->get('/search', http_build_query($data));
  276. $this->assertSame($response, $result);
  277. }
  278. /**
  279. * Test a GET with a request body. Services like
  280. * elasticsearch use this feature.
  281. *
  282. * @return void
  283. */
  284. public function testGetWithContent()
  285. {
  286. $response = new Response();
  287. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  288. ->setMethods(['send'])
  289. ->getMock();
  290. $mock->expects($this->once())
  291. ->method('send')
  292. ->with($this->callback(function ($request) {
  293. $this->assertEquals(Request::METHOD_GET, $request->getMethod());
  294. $this->assertEquals('http://cakephp.org/search', '' . $request->getUri());
  295. $this->assertEquals('some data', '' . $request->getBody());
  296. return true;
  297. }))
  298. ->will($this->returnValue([$response]));
  299. $http = new Client([
  300. 'host' => 'cakephp.org',
  301. 'adapter' => $mock
  302. ]);
  303. $result = $http->get('/search', [
  304. '_content' => 'some data'
  305. ]);
  306. $this->assertSame($result, $response);
  307. }
  308. /**
  309. * Test invalid authentication types throw exceptions.
  310. *
  311. * @expectedException \Cake\Core\Exception\Exception
  312. * @return void
  313. */
  314. public function testInvalidAuthenticationType()
  315. {
  316. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  317. ->setMethods(['send'])
  318. ->getMock();
  319. $mock->expects($this->never())
  320. ->method('send');
  321. $http = new Client([
  322. 'host' => 'cakephp.org',
  323. 'adapter' => $mock
  324. ]);
  325. $result = $http->get('/', [], [
  326. 'auth' => ['type' => 'horribly broken']
  327. ]);
  328. }
  329. /**
  330. * Test setting basic authentication with get
  331. *
  332. * @return void
  333. */
  334. public function testGetWithAuthenticationAndProxy()
  335. {
  336. $response = new Response();
  337. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  338. ->setMethods(['send'])
  339. ->getMock();
  340. $headers = [
  341. 'Authorization' => 'Basic ' . base64_encode('mark:secret'),
  342. 'Proxy-Authorization' => 'Basic ' . base64_encode('mark:pass'),
  343. ];
  344. $mock->expects($this->once())
  345. ->method('send')
  346. ->with($this->callback(function ($request) use ($headers) {
  347. $this->assertEquals(Request::METHOD_GET, $request->getMethod());
  348. $this->assertEquals('http://cakephp.org/', '' . $request->getUri());
  349. $this->assertEquals($headers['Authorization'], $request->getHeaderLine('Authorization'));
  350. $this->assertEquals($headers['Proxy-Authorization'], $request->getHeaderLine('Proxy-Authorization'));
  351. return true;
  352. }))
  353. ->will($this->returnValue([$response]));
  354. $http = new Client([
  355. 'host' => 'cakephp.org',
  356. 'adapter' => $mock
  357. ]);
  358. $result = $http->get('/', [], [
  359. 'auth' => ['username' => 'mark', 'password' => 'secret'],
  360. 'proxy' => ['username' => 'mark', 'password' => 'pass'],
  361. ]);
  362. $this->assertSame($result, $response);
  363. }
  364. /**
  365. * Test authentication adapter that mutates request.
  366. *
  367. * @return void
  368. */
  369. public function testAuthenticationWithMutation()
  370. {
  371. Configure::write('App.namespace', 'TestApp');
  372. $response = new Response();
  373. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  374. ->setMethods(['send'])
  375. ->getMock();
  376. $headers = [
  377. 'Authorization' => 'Bearer abc123',
  378. 'Proxy-Authorization' => 'Bearer abc123',
  379. ];
  380. $mock->expects($this->once())
  381. ->method('send')
  382. ->with($this->callback(function ($request) use ($headers) {
  383. $this->assertEquals(Request::METHOD_GET, $request->getMethod());
  384. $this->assertEquals('http://cakephp.org/', '' . $request->getUri());
  385. $this->assertEquals($headers['Authorization'], $request->getHeaderLine('Authorization'));
  386. $this->assertEquals($headers['Proxy-Authorization'], $request->getHeaderLine('Proxy-Authorization'));
  387. return true;
  388. }))
  389. ->will($this->returnValue([$response]));
  390. $http = new Client([
  391. 'host' => 'cakephp.org',
  392. 'adapter' => $mock
  393. ]);
  394. $result = $http->get('/', [], [
  395. 'auth' => ['type' => 'TestApp\Http\CompatAuth'],
  396. 'proxy' => ['type' => 'TestApp\Http\CompatAuth'],
  397. ]);
  398. $this->assertSame($result, $response);
  399. }
  400. /**
  401. * Return a list of HTTP methods.
  402. *
  403. * @return array
  404. */
  405. public static function methodProvider()
  406. {
  407. return [
  408. [Request::METHOD_GET],
  409. [Request::METHOD_POST],
  410. [Request::METHOD_PUT],
  411. [Request::METHOD_DELETE],
  412. [Request::METHOD_PATCH],
  413. [Request::METHOD_OPTIONS],
  414. [Request::METHOD_TRACE],
  415. ];
  416. }
  417. /**
  418. * test simple POST request.
  419. *
  420. * @dataProvider methodProvider
  421. * @return void
  422. */
  423. public function testMethodsSimple($method)
  424. {
  425. $response = new Response();
  426. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  427. ->setMethods(['send'])
  428. ->getMock();
  429. $mock->expects($this->once())
  430. ->method('send')
  431. ->with($this->callback(function ($request) use ($method) {
  432. $this->assertInstanceOf('Cake\Http\Client\Request', $request);
  433. $this->assertEquals($method, $request->getMethod());
  434. $this->assertEquals('http://cakephp.org/projects/add', '' . $request->getUri());
  435. return true;
  436. }))
  437. ->will($this->returnValue([$response]));
  438. $http = new Client([
  439. 'host' => 'cakephp.org',
  440. 'adapter' => $mock
  441. ]);
  442. $result = $http->{$method}('/projects/add');
  443. $this->assertSame($result, $response);
  444. }
  445. /**
  446. * Provider for testing the type option.
  447. *
  448. * @return array
  449. */
  450. public static function typeProvider()
  451. {
  452. return [
  453. ['application/json', 'application/json'],
  454. ['json', 'application/json'],
  455. ['xml', 'application/xml'],
  456. ['application/xml', 'application/xml'],
  457. ];
  458. }
  459. /**
  460. * Test that using the 'type' option sets the correct headers
  461. *
  462. * @dataProvider typeProvider
  463. * @return void
  464. */
  465. public function testPostWithTypeKey($type, $mime)
  466. {
  467. $response = new Response();
  468. $data = 'some data';
  469. $headers = [
  470. 'Content-Type' => $mime,
  471. 'Accept' => $mime,
  472. ];
  473. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  474. ->setMethods(['send'])
  475. ->getMock();
  476. $mock->expects($this->once())
  477. ->method('send')
  478. ->with($this->callback(function ($request) use ($headers) {
  479. $this->assertEquals(Request::METHOD_POST, $request->getMethod());
  480. $this->assertEquals($headers['Content-Type'], $request->getHeaderLine('Content-Type'));
  481. $this->assertEquals($headers['Accept'], $request->getHeaderLine('Accept'));
  482. return true;
  483. }))
  484. ->will($this->returnValue([$response]));
  485. $http = new Client([
  486. 'host' => 'cakephp.org',
  487. 'adapter' => $mock
  488. ]);
  489. $http->post('/projects/add', $data, ['type' => $type]);
  490. }
  491. /**
  492. * Test that string payloads with no content type have a default content-type set.
  493. *
  494. * @return void
  495. */
  496. public function testPostWithStringDataDefaultsToFormEncoding()
  497. {
  498. $response = new Response();
  499. $data = 'some=value&more=data';
  500. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  501. ->setMethods(['send'])
  502. ->getMock();
  503. $mock->expects($this->any())
  504. ->method('send')
  505. ->with($this->callback(function ($request) use ($data) {
  506. $this->assertEquals($data, '' . $request->getBody());
  507. $this->assertEquals('application/x-www-form-urlencoded', $request->getHeaderLine('content-type'));
  508. return true;
  509. }))
  510. ->will($this->returnValue([$response]));
  511. $http = new Client([
  512. 'host' => 'cakephp.org',
  513. 'adapter' => $mock
  514. ]);
  515. $http->post('/projects/add', $data);
  516. $http->put('/projects/add', $data);
  517. $http->delete('/projects/add', $data);
  518. }
  519. /**
  520. * Test that exceptions are raised on invalid types.
  521. *
  522. * @expectedException \Cake\Core\Exception\Exception
  523. * @return void
  524. */
  525. public function testExceptionOnUnknownType()
  526. {
  527. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  528. ->setMethods(['send'])
  529. ->getMock();
  530. $mock->expects($this->never())
  531. ->method('send');
  532. $http = new Client([
  533. 'host' => 'cakephp.org',
  534. 'adapter' => $mock
  535. ]);
  536. $http->post('/projects/add', 'it works', ['type' => 'invalid']);
  537. }
  538. /**
  539. * Test that Client stores cookies
  540. *
  541. * @return void
  542. */
  543. public function testCookieStorage()
  544. {
  545. $adapter = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  546. ->setMethods(['send'])
  547. ->getMock();
  548. $headers = [
  549. 'HTTP/1.0 200 Ok',
  550. 'Set-Cookie: first=1',
  551. 'Set-Cookie: expiring=now; Expires=Wed, 09-Jun-1999 10:18:14 GMT'
  552. ];
  553. $response = new Response($headers, '');
  554. $adapter->expects($this->at(0))
  555. ->method('send')
  556. ->will($this->returnValue([$response]));
  557. $http = new Client([
  558. 'host' => 'cakephp.org',
  559. 'adapter' => $adapter,
  560. ]);
  561. $http->get('/projects');
  562. $cookies = $http->cookies();
  563. $this->assertCount(1, $cookies);
  564. $this->assertTrue($cookies->has('first'));
  565. $this->assertFalse($cookies->has('expiring'));
  566. }
  567. /**
  568. * Test cookieJar config option.
  569. *
  570. * @return void
  571. */
  572. public function testCookieJar()
  573. {
  574. $jar = new CookieCollection();
  575. $http = new Client([
  576. 'cookieJar' => $jar
  577. ]);
  578. $this->assertSame($jar, $http->cookies());
  579. }
  580. /**
  581. * Test addCookie() method.
  582. *
  583. * @return void
  584. */
  585. public function testAddCookie()
  586. {
  587. $client = new Client();
  588. $cookie = new Cookie('foo');
  589. $this->assertFalse($client->cookies()->has('foo'));
  590. $client->addCookie($cookie);
  591. $this->assertTrue($client->cookies()->has('foo'));
  592. }
  593. /**
  594. * test head request with querystring data
  595. *
  596. * @return void
  597. */
  598. public function testHeadQuerystring()
  599. {
  600. $response = new Response();
  601. $mock = $this->getMockBuilder('Cake\Http\Client\Adapter\Stream')
  602. ->setMethods(['send'])
  603. ->getMock();
  604. $mock->expects($this->once())
  605. ->method('send')
  606. ->with($this->callback(function ($request) {
  607. $this->assertInstanceOf('Cake\Http\Client\Request', $request);
  608. $this->assertEquals(Request::METHOD_HEAD, $request->getMethod());
  609. $this->assertEquals('http://cakephp.org/search?q=hi+there', '' . $request->getUri());
  610. return true;
  611. }))
  612. ->will($this->returnValue([$response]));
  613. $http = new Client([
  614. 'host' => 'cakephp.org',
  615. 'adapter' => $mock
  616. ]);
  617. $result = $http->head('/search', [
  618. 'q' => 'hi there',
  619. ]);
  620. $this->assertSame($result, $response);
  621. }
  622. }