ClientTest.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.0.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\TestCase\Http;
  16. use Cake\Http\Client;
  17. use Cake\Http\Client\Adapter\Stream;
  18. use Cake\Http\Client\Request;
  19. use Cake\Http\Client\Response;
  20. use Cake\Http\Cookie\Cookie;
  21. use Cake\Http\Cookie\CookieCollection;
  22. use Cake\TestSuite\TestCase;
  23. use InvalidArgumentException;
  24. /**
  25. * HTTP client test.
  26. */
  27. class ClientTest extends TestCase
  28. {
  29. /**
  30. * Test storing config options and modifying them.
  31. *
  32. * @return void
  33. */
  34. public function testConstructConfig()
  35. {
  36. $config = [
  37. 'scheme' => 'http',
  38. 'host' => 'example.org',
  39. 'basePath' => '/api/v1',
  40. ];
  41. $http = new Client($config);
  42. $result = $http->getConfig();
  43. foreach ($config as $key => $val) {
  44. $this->assertEquals($val, $result[$key]);
  45. }
  46. $result = $http->setConfig([
  47. 'auth' => ['username' => 'mark', 'password' => 'secret'],
  48. ]);
  49. $this->assertSame($result, $http);
  50. $result = $http->getConfig();
  51. $expected = [
  52. 'scheme' => 'http',
  53. 'host' => 'example.org',
  54. 'auth' => ['username' => 'mark', 'password' => 'secret'],
  55. 'protocolVersion' => '1.1',
  56. ];
  57. foreach ($expected as $key => $val) {
  58. $this->assertEquals($val, $result[$key]);
  59. }
  60. }
  61. /**
  62. * testAdapterInstanceCheck
  63. *
  64. * @return void
  65. */
  66. public function testAdapterInstanceCheck()
  67. {
  68. $this->expectException(InvalidArgumentException::class);
  69. $this->expectExceptionMessage('Adapter must be an instance of Cake\Http\Client\AdapterInterface');
  70. new Client(['adapter' => 'stdClass']);
  71. }
  72. /**
  73. * Data provider for buildUrl() tests
  74. *
  75. * @return array
  76. */
  77. public static function urlProvider()
  78. {
  79. return [
  80. [
  81. 'http://example.com/test.html',
  82. 'http://example.com/test.html',
  83. [],
  84. null,
  85. 'Null options',
  86. ],
  87. [
  88. 'http://example.com/test.html',
  89. 'http://example.com/test.html',
  90. [],
  91. [],
  92. 'Simple string',
  93. ],
  94. [
  95. 'http://example.com/test.html',
  96. '/test.html',
  97. [],
  98. ['host' => 'example.com'],
  99. 'host name option',
  100. ],
  101. [
  102. 'https://example.com/test.html',
  103. '/test.html',
  104. [],
  105. ['host' => 'example.com', 'scheme' => 'https'],
  106. 'HTTPS',
  107. ],
  108. [
  109. 'https://example.com/api/v1/foo/test.html',
  110. '/foo/test.html',
  111. [],
  112. ['host' => 'example.com', 'scheme' => 'https', 'basePath' => '/api/v1'],
  113. 'Base path included',
  114. ],
  115. [
  116. 'https://example.com/api/v1/foo/test.html',
  117. '/foo/test.html',
  118. [],
  119. ['host' => 'example.com', 'scheme' => 'https', 'basePath' => '/api/v1/'],
  120. 'Base path with trailing forward slash',
  121. ],
  122. [
  123. 'https://example.com/api/v1/foo/test.html',
  124. '/foo/test.html',
  125. [],
  126. ['host' => 'example.com', 'scheme' => 'https', 'basePath' => 'api/v1/'],
  127. 'Base path with no prepended forward slash',
  128. ],
  129. [
  130. 'http://example.com:8080/test.html',
  131. '/test.html',
  132. [],
  133. ['host' => 'example.com', 'port' => '8080'],
  134. 'Non standard port',
  135. ],
  136. [
  137. 'http://example.com/test.html',
  138. '/test.html',
  139. [],
  140. ['host' => 'example.com', 'port' => '80'],
  141. 'standard port, does not display',
  142. ],
  143. [
  144. 'https://example.com/test.html',
  145. '/test.html',
  146. [],
  147. ['host' => 'example.com', 'scheme' => 'https', 'port' => '443'],
  148. 'standard port, does not display',
  149. ],
  150. [
  151. 'http://example.com/test.html',
  152. 'http://example.com/test.html',
  153. [],
  154. ['host' => 'example.com', 'scheme' => 'https'],
  155. 'options do not duplicate',
  156. ],
  157. [
  158. 'http://example.com/search?q=hi+there&cat%5Bid%5D%5B0%5D=2&cat%5Bid%5D%5B1%5D=3',
  159. 'http://example.com/search',
  160. ['q' => 'hi there', 'cat' => ['id' => [2, 3]]],
  161. [],
  162. 'query string data.',
  163. ],
  164. [
  165. 'http://example.com/search?q=hi+there&id=12',
  166. 'http://example.com/search?q=hi+there',
  167. ['id' => '12'],
  168. [],
  169. 'query string data with some already on the url.',
  170. ],
  171. [
  172. 'http://example.com/test.html',
  173. '//test.html',
  174. [],
  175. [
  176. 'scheme' => 'http',
  177. 'host' => 'example.com',
  178. 'protocolRelative' => false,
  179. ],
  180. 'url with a double slash',
  181. ],
  182. [
  183. 'http://example.com/test.html',
  184. '//example.com/test.html',
  185. [],
  186. [
  187. 'scheme' => 'http',
  188. 'protocolRelative' => true,
  189. ],
  190. 'protocol relative url',
  191. ],
  192. ];
  193. }
  194. /**
  195. * @dataProvider urlProvider
  196. */
  197. public function testBuildUrl($expected, $url, $query, $opts)
  198. {
  199. $http = new Client();
  200. $result = $http->buildUrl($url, $query, (array)$opts);
  201. $this->assertEquals($expected, $result);
  202. }
  203. /**
  204. * test simple get request with headers & cookies.
  205. *
  206. * @return void
  207. */
  208. public function testGetSimpleWithHeadersAndCookies()
  209. {
  210. $response = new Response();
  211. $headers = [
  212. 'User-Agent' => 'Cake',
  213. 'Connection' => 'close',
  214. 'Content-Type' => 'application/x-www-form-urlencoded',
  215. ];
  216. $cookies = [
  217. 'split' => 'value',
  218. ];
  219. $mock = $this->getMockBuilder(Stream::class)
  220. ->onlyMethods(['send'])
  221. ->getMock();
  222. $mock->expects($this->once())
  223. ->method('send')
  224. ->with($this->callback(function ($request) use ($headers) {
  225. $this->assertInstanceOf('Cake\Http\Client\Request', $request);
  226. $this->assertSame(Request::METHOD_GET, $request->getMethod());
  227. $this->assertSame('2', $request->getProtocolVersion());
  228. $this->assertSame('http://cakephp.org/test.html', $request->getUri() . '');
  229. $this->assertSame('split=value', $request->getHeaderLine('Cookie'));
  230. $this->assertSame($headers['Content-Type'], $request->getHeaderLine('content-type'));
  231. $this->assertSame($headers['Connection'], $request->getHeaderLine('connection'));
  232. return true;
  233. }))
  234. ->will($this->returnValue([$response]));
  235. $http = new Client(['adapter' => $mock, 'protocolVersion' => '2']);
  236. $result = $http->get('http://cakephp.org/test.html', [], [
  237. 'headers' => $headers,
  238. 'cookies' => $cookies,
  239. ]);
  240. $this->assertSame($result, $response);
  241. }
  242. /**
  243. * test get request with no data
  244. *
  245. * @return void
  246. */
  247. public function testGetNoData()
  248. {
  249. $response = new Response();
  250. $mock = $this->getMockBuilder(Stream::class)
  251. ->onlyMethods(['send'])
  252. ->getMock();
  253. $mock->expects($this->once())
  254. ->method('send')
  255. ->with($this->callback(function ($request) {
  256. $this->assertSame(Request::METHOD_GET, $request->getMethod());
  257. $this->assertEmpty($request->getHeaderLine('Content-Type'), 'Should have no content-type set');
  258. $this->assertSame(
  259. 'http://cakephp.org/search',
  260. $request->getUri() . ''
  261. );
  262. return true;
  263. }))
  264. ->will($this->returnValue([$response]));
  265. $http = new Client([
  266. 'host' => 'cakephp.org',
  267. 'adapter' => $mock,
  268. ]);
  269. $result = $http->get('/search');
  270. $this->assertSame($result, $response);
  271. }
  272. /**
  273. * test get request with querystring data
  274. *
  275. * @return void
  276. */
  277. public function testGetQuerystring()
  278. {
  279. $response = new Response();
  280. $mock = $this->getMockBuilder(Stream::class)
  281. ->onlyMethods(['send'])
  282. ->getMock();
  283. $mock->expects($this->once())
  284. ->method('send')
  285. ->with($this->callback(function ($request) {
  286. $this->assertSame(Request::METHOD_GET, $request->getMethod());
  287. $this->assertSame(
  288. 'http://cakephp.org/search?q=hi+there&Category%5Bid%5D%5B0%5D=2&Category%5Bid%5D%5B1%5D=3',
  289. $request->getUri() . ''
  290. );
  291. return true;
  292. }))
  293. ->will($this->returnValue([$response]));
  294. $http = new Client([
  295. 'host' => 'cakephp.org',
  296. 'adapter' => $mock,
  297. ]);
  298. $result = $http->get('/search', [
  299. 'q' => 'hi there',
  300. 'Category' => ['id' => [2, 3]],
  301. ]);
  302. $this->assertSame($result, $response);
  303. }
  304. /**
  305. * test get request with string of query data.
  306. *
  307. * @return void
  308. */
  309. public function testGetQuerystringString()
  310. {
  311. $response = new Response();
  312. $mock = $this->getMockBuilder(Stream::class)
  313. ->onlyMethods(['send'])
  314. ->getMock();
  315. $mock->expects($this->once())
  316. ->method('send')
  317. ->with($this->callback(function ($request) {
  318. $this->assertSame(
  319. 'http://cakephp.org/search?q=hi+there&Category%5Bid%5D%5B0%5D=2&Category%5Bid%5D%5B1%5D=3',
  320. $request->getUri() . ''
  321. );
  322. return true;
  323. }))
  324. ->will($this->returnValue([$response]));
  325. $http = new Client([
  326. 'host' => 'cakephp.org',
  327. 'adapter' => $mock,
  328. ]);
  329. $data = [
  330. 'q' => 'hi there',
  331. 'Category' => ['id' => [2, 3]],
  332. ];
  333. $result = $http->get('/search', http_build_query($data));
  334. $this->assertSame($response, $result);
  335. }
  336. /**
  337. * Test a GET with a request body. Services like
  338. * elasticsearch use this feature.
  339. *
  340. * @return void
  341. */
  342. public function testGetWithContent()
  343. {
  344. $response = new Response();
  345. $mock = $this->getMockBuilder(Stream::class)
  346. ->onlyMethods(['send'])
  347. ->getMock();
  348. $mock->expects($this->once())
  349. ->method('send')
  350. ->with($this->callback(function ($request) {
  351. $this->assertSame(Request::METHOD_GET, $request->getMethod());
  352. $this->assertSame('http://cakephp.org/search', '' . $request->getUri());
  353. $this->assertSame('some data', '' . $request->getBody());
  354. return true;
  355. }))
  356. ->will($this->returnValue([$response]));
  357. $http = new Client([
  358. 'host' => 'cakephp.org',
  359. 'adapter' => $mock,
  360. ]);
  361. $result = $http->get('/search', [
  362. '_content' => 'some data',
  363. ]);
  364. $this->assertSame($result, $response);
  365. }
  366. /**
  367. * Test invalid authentication types throw exceptions.
  368. *
  369. * @return void
  370. */
  371. public function testInvalidAuthenticationType()
  372. {
  373. $this->expectException(\Cake\Core\Exception\CakeException::class);
  374. $mock = $this->getMockBuilder(Stream::class)
  375. ->onlyMethods(['send'])
  376. ->getMock();
  377. $mock->expects($this->never())
  378. ->method('send');
  379. $http = new Client([
  380. 'host' => 'cakephp.org',
  381. 'adapter' => $mock,
  382. ]);
  383. $result = $http->get('/', [], [
  384. 'auth' => ['type' => 'horribly broken'],
  385. ]);
  386. }
  387. /**
  388. * Test setting basic authentication with get
  389. *
  390. * @return void
  391. */
  392. public function testGetWithAuthenticationAndProxy()
  393. {
  394. $response = new Response();
  395. $mock = $this->getMockBuilder(Stream::class)
  396. ->onlyMethods(['send'])
  397. ->getMock();
  398. $headers = [
  399. 'Authorization' => 'Basic ' . base64_encode('mark:secret'),
  400. 'Proxy-Authorization' => 'Basic ' . base64_encode('mark:pass'),
  401. ];
  402. $mock->expects($this->once())
  403. ->method('send')
  404. ->with($this->callback(function ($request) use ($headers) {
  405. $this->assertSame(Request::METHOD_GET, $request->getMethod());
  406. $this->assertSame('http://cakephp.org/', '' . $request->getUri());
  407. $this->assertSame($headers['Authorization'], $request->getHeaderLine('Authorization'));
  408. $this->assertSame($headers['Proxy-Authorization'], $request->getHeaderLine('Proxy-Authorization'));
  409. return true;
  410. }))
  411. ->will($this->returnValue([$response]));
  412. $http = new Client([
  413. 'host' => 'cakephp.org',
  414. 'adapter' => $mock,
  415. ]);
  416. $result = $http->get('/', [], [
  417. 'auth' => ['username' => 'mark', 'password' => 'secret'],
  418. 'proxy' => ['username' => 'mark', 'password' => 'pass'],
  419. ]);
  420. $this->assertSame($result, $response);
  421. }
  422. /**
  423. * Return a list of HTTP methods.
  424. *
  425. * @return array
  426. */
  427. public static function methodProvider()
  428. {
  429. return [
  430. [Request::METHOD_GET],
  431. [Request::METHOD_POST],
  432. [Request::METHOD_PUT],
  433. [Request::METHOD_DELETE],
  434. [Request::METHOD_PATCH],
  435. [Request::METHOD_OPTIONS],
  436. [Request::METHOD_TRACE],
  437. ];
  438. }
  439. /**
  440. * test simple POST request.
  441. *
  442. * @dataProvider methodProvider
  443. * @return void
  444. */
  445. public function testMethodsSimple($method)
  446. {
  447. $response = new Response();
  448. $mock = $this->getMockBuilder(Stream::class)
  449. ->onlyMethods(['send'])
  450. ->getMock();
  451. $mock->expects($this->once())
  452. ->method('send')
  453. ->with($this->callback(function ($request) use ($method) {
  454. $this->assertInstanceOf('Cake\Http\Client\Request', $request);
  455. $this->assertEquals($method, $request->getMethod());
  456. $this->assertSame('http://cakephp.org/projects/add', '' . $request->getUri());
  457. return true;
  458. }))
  459. ->will($this->returnValue([$response]));
  460. $http = new Client([
  461. 'host' => 'cakephp.org',
  462. 'adapter' => $mock,
  463. ]);
  464. $result = $http->{$method}('/projects/add');
  465. $this->assertSame($result, $response);
  466. }
  467. /**
  468. * Provider for testing the type option.
  469. *
  470. * @return array
  471. */
  472. public static function typeProvider()
  473. {
  474. return [
  475. ['application/json', 'application/json'],
  476. ['json', 'application/json'],
  477. ['xml', 'application/xml'],
  478. ['application/xml', 'application/xml'],
  479. ];
  480. }
  481. /**
  482. * Test that using the 'type' option sets the correct headers
  483. *
  484. * @dataProvider typeProvider
  485. * @return void
  486. */
  487. public function testPostWithTypeKey($type, $mime)
  488. {
  489. $response = new Response();
  490. $data = 'some data';
  491. $headers = [
  492. 'Content-Type' => $mime,
  493. 'Accept' => $mime,
  494. ];
  495. $mock = $this->getMockBuilder(Stream::class)
  496. ->onlyMethods(['send'])
  497. ->getMock();
  498. $mock->expects($this->once())
  499. ->method('send')
  500. ->with($this->callback(function ($request) use ($headers) {
  501. $this->assertSame(Request::METHOD_POST, $request->getMethod());
  502. $this->assertEquals($headers['Content-Type'], $request->getHeaderLine('Content-Type'));
  503. $this->assertEquals($headers['Accept'], $request->getHeaderLine('Accept'));
  504. return true;
  505. }))
  506. ->will($this->returnValue([$response]));
  507. $http = new Client([
  508. 'host' => 'cakephp.org',
  509. 'adapter' => $mock,
  510. ]);
  511. $http->post('/projects/add', $data, ['type' => $type]);
  512. }
  513. /**
  514. * Test that string payloads with no content type have a default content-type set.
  515. *
  516. * @return void
  517. */
  518. public function testPostWithStringDataDefaultsToFormEncoding()
  519. {
  520. $response = new Response();
  521. $data = 'some=value&more=data';
  522. $mock = $this->getMockBuilder(Stream::class)
  523. ->onlyMethods(['send'])
  524. ->getMock();
  525. $mock->expects($this->any())
  526. ->method('send')
  527. ->with($this->callback(function ($request) use ($data) {
  528. $this->assertSame($data, '' . $request->getBody());
  529. $this->assertSame('application/x-www-form-urlencoded', $request->getHeaderLine('content-type'));
  530. return true;
  531. }))
  532. ->will($this->returnValue([$response]));
  533. $http = new Client([
  534. 'host' => 'cakephp.org',
  535. 'adapter' => $mock,
  536. ]);
  537. $http->post('/projects/add', $data);
  538. $http->put('/projects/add', $data);
  539. $http->delete('/projects/add', $data);
  540. }
  541. /**
  542. * Test that exceptions are raised on invalid types.
  543. *
  544. * @return void
  545. */
  546. public function testExceptionOnUnknownType()
  547. {
  548. $this->expectException(\Cake\Core\Exception\CakeException::class);
  549. $mock = $this->getMockBuilder(Stream::class)
  550. ->onlyMethods(['send'])
  551. ->getMock();
  552. $mock->expects($this->never())
  553. ->method('send');
  554. $http = new Client([
  555. 'host' => 'cakephp.org',
  556. 'adapter' => $mock,
  557. ]);
  558. $http->post('/projects/add', 'it works', ['type' => 'invalid']);
  559. }
  560. /**
  561. * Test that Client stores cookies
  562. *
  563. * @return void
  564. */
  565. public function testCookieStorage()
  566. {
  567. $adapter = $this->getMockBuilder(Stream::class)
  568. ->onlyMethods(['send'])
  569. ->getMock();
  570. $headers = [
  571. 'HTTP/1.0 200 Ok',
  572. 'Set-Cookie: first=1',
  573. 'Set-Cookie: expiring=now; Expires=Wed, 09-Jun-1999 10:18:14 GMT',
  574. ];
  575. $response = new Response($headers, '');
  576. $adapter->expects($this->at(0))
  577. ->method('send')
  578. ->will($this->returnValue([$response]));
  579. $http = new Client([
  580. 'host' => 'cakephp.org',
  581. 'adapter' => $adapter,
  582. ]);
  583. $http->get('/projects');
  584. $cookies = $http->cookies();
  585. $this->assertCount(1, $cookies);
  586. $this->assertTrue($cookies->has('first'));
  587. $this->assertFalse($cookies->has('expiring'));
  588. }
  589. /**
  590. * Test cookieJar config option.
  591. *
  592. * @return void
  593. */
  594. public function testCookieJar()
  595. {
  596. $jar = new CookieCollection();
  597. $http = new Client([
  598. 'cookieJar' => $jar,
  599. ]);
  600. $this->assertSame($jar, $http->cookies());
  601. }
  602. /**
  603. * Test addCookie() method.
  604. *
  605. * @return void
  606. */
  607. public function testAddCookie()
  608. {
  609. $client = new Client();
  610. $cookie = new Cookie('foo', '', null, '/', 'example.com');
  611. $this->assertFalse($client->cookies()->has('foo'));
  612. $client->addCookie($cookie);
  613. $this->assertTrue($client->cookies()->has('foo'));
  614. }
  615. /**
  616. * Test addCookie() method without a domain.
  617. *
  618. * @return void
  619. */
  620. public function testAddCookieWithoutDomain()
  621. {
  622. $this->expectException(\InvalidArgumentException::class);
  623. $this->expectExceptionMessage('Cookie must have a domain and a path set.');
  624. $client = new Client();
  625. $cookie = new Cookie('foo', '', null, '/', '');
  626. $this->assertFalse($client->cookies()->has('foo'));
  627. $client->addCookie($cookie);
  628. $this->assertTrue($client->cookies()->has('foo'));
  629. }
  630. /**
  631. * Test addCookie() method without a path.
  632. *
  633. * @return void
  634. */
  635. public function testAddCookieWithoutPath()
  636. {
  637. $this->expectException(\InvalidArgumentException::class);
  638. $this->expectExceptionMessage('Cookie must have a domain and a path set.');
  639. $client = new Client();
  640. $cookie = new Cookie('foo', '', null, '', 'example.com');
  641. $this->assertFalse($client->cookies()->has('foo'));
  642. $client->addCookie($cookie);
  643. $this->assertTrue($client->cookies()->has('foo'));
  644. }
  645. /**
  646. * test head request with querystring data
  647. *
  648. * @return void
  649. */
  650. public function testHeadQuerystring()
  651. {
  652. $response = new Response();
  653. $mock = $this->getMockBuilder(Stream::class)
  654. ->onlyMethods(['send'])
  655. ->getMock();
  656. $mock->expects($this->once())
  657. ->method('send')
  658. ->with($this->callback(function ($request) {
  659. $this->assertInstanceOf('Cake\Http\Client\Request', $request);
  660. $this->assertSame(Request::METHOD_HEAD, $request->getMethod());
  661. $this->assertSame('http://cakephp.org/search?q=hi+there', '' . $request->getUri());
  662. return true;
  663. }))
  664. ->will($this->returnValue([$response]));
  665. $http = new Client([
  666. 'host' => 'cakephp.org',
  667. 'adapter' => $mock,
  668. ]);
  669. $result = $http->head('/search', [
  670. 'q' => 'hi there',
  671. ]);
  672. $this->assertSame($result, $response);
  673. }
  674. /**
  675. * test redirects
  676. *
  677. * @return void
  678. */
  679. public function testRedirects()
  680. {
  681. $url = 'http://cakephp.org';
  682. $adapter = $this->getMockBuilder(Client\Adapter\Stream::class)
  683. ->onlyMethods(['send'])
  684. ->getMock();
  685. $redirect = new Response([
  686. 'HTTP/1.0 301',
  687. 'Location: http://cakephp.org/redirect1?foo=bar',
  688. 'Set-Cookie: redirect1=true;path=/',
  689. ]);
  690. $adapter->expects($this->at(0))
  691. ->method('send')
  692. ->with(
  693. $this->callback(function (Request $request) use ($url) {
  694. $this->assertInstanceOf(Request::class, $request);
  695. $this->assertSame($url, (string)$request->getUri());
  696. return true;
  697. }),
  698. $this->callback(function ($options) {
  699. $this->assertArrayNotHasKey('redirect', $options);
  700. return true;
  701. })
  702. )
  703. ->willReturn([$redirect]);
  704. $redirect2 = new Response([
  705. 'HTTP/1.0 301',
  706. 'Location: /redirect2#foo',
  707. 'Set-Cookie: redirect2=true;path=/',
  708. ]);
  709. $adapter->expects($this->at(1))
  710. ->method('send')
  711. ->with(
  712. $this->callback(function (Request $request) use ($url) {
  713. $this->assertInstanceOf(Request::class, $request);
  714. $this->assertSame($url . '/redirect1?foo=bar', (string)$request->getUri());
  715. return true;
  716. }),
  717. $this->callback(function ($options) {
  718. $this->assertArrayNotHasKey('redirect', $options);
  719. return true;
  720. })
  721. )
  722. ->willReturn([$redirect2]);
  723. $response = new Response([
  724. 'HTTP/1.0 200',
  725. ]);
  726. $adapter->expects($this->at(2))
  727. ->method('send')
  728. ->with($this->callback(function (Request $request) use ($url) {
  729. $this->assertInstanceOf(Request::class, $request);
  730. $this->assertSame($url . '/redirect2#foo', (string)$request->getUri());
  731. return true;
  732. }))
  733. ->willReturn([$response]);
  734. $client = new Client([
  735. 'adapter' => $adapter,
  736. ]);
  737. $result = $client->send(new Request($url), [
  738. 'redirect' => 10,
  739. ]);
  740. $this->assertInstanceOf(Response::class, $result);
  741. $this->assertTrue($result->isOk());
  742. $cookies = $client->cookies();
  743. $this->assertTrue($cookies->has('redirect1'));
  744. $this->assertTrue($cookies->has('redirect2'));
  745. }
  746. /**
  747. * testSendRequest
  748. *
  749. * @return void
  750. */
  751. public function testSendRequest()
  752. {
  753. $response = new Response();
  754. $headers = [
  755. 'User-Agent' => 'Cake',
  756. 'Connection' => 'close',
  757. 'Content-Type' => 'application/x-www-form-urlencoded',
  758. ];
  759. $mock = $this->getMockBuilder(Stream::class)
  760. ->onlyMethods(['send'])
  761. ->getMock();
  762. $mock->expects($this->once())
  763. ->method('send')
  764. ->with($this->callback(function ($request) use ($headers) {
  765. $this->assertInstanceOf('Laminas\Diactoros\Request', $request);
  766. $this->assertSame(Request::METHOD_GET, $request->getMethod());
  767. $this->assertSame('http://cakephp.org/test.html', $request->getUri() . '');
  768. $this->assertSame($headers['Content-Type'], $request->getHeaderLine('content-type'));
  769. $this->assertSame($headers['Connection'], $request->getHeaderLine('connection'));
  770. return true;
  771. }))
  772. ->will($this->returnValue([$response]));
  773. $http = new Client(['adapter' => $mock]);
  774. $request = new \Laminas\Diactoros\Request(
  775. 'http://cakephp.org/test.html',
  776. Request::METHOD_GET,
  777. 'php://temp',
  778. $headers
  779. );
  780. $result = $http->sendRequest($request);
  781. $this->assertSame($result, $response);
  782. }
  783. /**
  784. * test redirect across sub domains
  785. *
  786. * @return void
  787. */
  788. public function testRedirectDifferentSubDomains()
  789. {
  790. $adapter = $this->getMockBuilder(Client\Adapter\Stream::class)
  791. ->onlyMethods(['send'])
  792. ->getMock();
  793. $url = 'http://auth.example.org';
  794. $redirect = new Response([
  795. 'HTTP/1.0 301',
  796. 'Location: http://backstage.example.org',
  797. ]);
  798. $adapter->expects($this->at(0))
  799. ->method('send')
  800. ->willReturn([$redirect]);
  801. $response = new Response([
  802. 'HTTP/1.0 200',
  803. ]);
  804. $adapter->expects($this->at(1))
  805. ->method('send')
  806. ->with($this->callback(function ($request) {
  807. $this->assertSame('http://backstage.example.org', (string)$request->getUri());
  808. $this->assertSame('session=backend', $request->getHeaderLine('Cookie'));
  809. return true;
  810. }))
  811. ->willReturn([$response]);
  812. $client = new Client([
  813. 'adapter' => $adapter,
  814. ]);
  815. $client->addCookie(new Cookie('session', 'backend', null, '/', 'backstage.example.org'));
  816. $client->addCookie(new Cookie('session', 'authz', null, '/', 'auth.example.org'));
  817. $result = $client->send(new Request($url), [
  818. 'redirect' => 10,
  819. ]);
  820. $this->assertInstanceOf(Response::class, $result);
  821. $this->assertSame($response, $result);
  822. }
  823. /**
  824. * Scheme is set when passed to client in string
  825. */
  826. public function testCreateFromUrlSetsScheme()
  827. {
  828. $client = Client::createFromUrl('https://example.co/');
  829. $this->assertSame('https', $client->getConfig('scheme'));
  830. }
  831. /**
  832. * Host is set when passed to client in string
  833. */
  834. public function testCreateFromUrlSetsHost()
  835. {
  836. $client = Client::createFromUrl('https://example.co/');
  837. $this->assertSame('example.co', $client->getConfig('host'));
  838. }
  839. /**
  840. * basePath is set when passed to client in string
  841. */
  842. public function testCreateFromUrlSetsBasePath()
  843. {
  844. $client = Client::createFromUrl('https://example.co/api/v1');
  845. $this->assertSame('/api/v1', $client->getConfig('basePath'));
  846. }
  847. /**
  848. * Test exception is thrown when URL cannot be parsed
  849. */
  850. public function testCreateFromUrlThrowsInvalidExceptionWhenUrlCannotBeParsed()
  851. {
  852. $this->expectException(InvalidArgumentException::class);
  853. Client::createFromUrl('htps://');
  854. $message = $this->getExpectedExceptionMessage();
  855. $this->assertTextContains('did not parse', $message);
  856. }
  857. /**
  858. * Port is set when passed to client in string
  859. */
  860. public function testCreateFromUrlSetsPort()
  861. {
  862. $client = Client::createFromUrl('https://example.co:8765/');
  863. $this->assertSame(8765, $client->getConfig('port'));
  864. }
  865. /**
  866. * Test exception is throw when no scheme is provided.
  867. */
  868. public function testCreateFromUrlThrowsInvalidArgumentExceptionWhenNoSchemeProvided()
  869. {
  870. $this->expectException(InvalidArgumentException::class);
  871. Client::createFromUrl('example.co');
  872. $message = $this->getExpectedExceptionMessage();
  873. $this->assertSame('The URL was parsed but did not contain a scheme or host', $message);
  874. }
  875. /**
  876. * Test exception is thrown if passed URL has no domain
  877. */
  878. public function testCreateFromUrlThrowsInvalidArgumentExceptionWhenNoDomainProvided()
  879. {
  880. $this->expectException(InvalidArgumentException::class);
  881. Client::createFromUrl('/api/v1');
  882. $message = $this->getExpectedExceptionMessage();
  883. $this->assertSame('The URL was parsed but did not contain a scheme or host', $message);
  884. }
  885. /**
  886. * Test that the passed parsed URL parts won't override other constructor defaults
  887. * or add undefined configuration
  888. */
  889. public function testCreateFromUrlOnlySetSchemePortHostBasePath()
  890. {
  891. $client = Client::createFromUrl('http://example.co:80/some/uri/?foo=bar');
  892. $config = $client->getConfig();
  893. $expected = [
  894. 'adapter' => null,
  895. 'host' => 'example.co',
  896. 'port' => 80,
  897. 'scheme' => 'http',
  898. 'basePath' => '/some/uri/',
  899. 'timeout' => 30,
  900. 'ssl_verify_peer' => true,
  901. 'ssl_verify_peer_name' => true,
  902. 'ssl_verify_depth' => 5,
  903. 'ssl_verify_host' => true,
  904. 'redirect' => false,
  905. 'protocolVersion' => '1.1',
  906. ];
  907. $this->assertSame($expected, $config);
  908. }
  909. }