1<?php
2// Override curl_setopt_array() to get the last set curl options
3namespace GuzzleHttp\Ring\Client {
4    function curl_setopt_array($handle, array $options) {
5        if (!empty($_SERVER['curl_test'])) {
6            $_SERVER['_curl'] = $options;
7        } else {
8            unset($_SERVER['_curl']);
9        }
10        \curl_setopt_array($handle, $options);
11    }
12}
13
14namespace GuzzleHttp\Tests\Ring\Client {
15
16use GuzzleHttp\Ring\Client\CurlFactory;
17use GuzzleHttp\Ring\Client\CurlMultiHandler;
18use GuzzleHttp\Ring\Client\MockHandler;
19use GuzzleHttp\Ring\Core;
20use GuzzleHttp\Stream\FnStream;
21use GuzzleHttp\Stream\NoSeekStream;
22use GuzzleHttp\Stream\Stream;
23
24class CurlFactoryTest extends \PHPUnit_Framework_TestCase
25{
26    public static function setUpBeforeClass()
27    {
28        $_SERVER['curl_test'] = true;
29        unset($_SERVER['_curl']);
30    }
31
32    public static function tearDownAfterClass()
33    {
34        unset($_SERVER['_curl'], $_SERVER['curl_test']);
35    }
36
37    public function testCreatesCurlHandle()
38    {
39        Server::flush();
40        Server::enqueue([[
41            'status' => 200,
42            'headers' => [
43                'Foo' => ['Bar'],
44                'Baz' => ['bam'],
45                'Content-Length' => [2],
46            ],
47            'body' => 'hi',
48        ]]);
49
50        $stream = Stream::factory();
51
52        $request = [
53            'http_method' => 'PUT',
54            'headers' => [
55                'host' => [Server::$url],
56                'Hi'   => [' 123'],
57            ],
58            'body' => 'testing',
59            'client' => ['save_to' => $stream],
60        ];
61
62        $f = new CurlFactory();
63        $result = $f($request);
64        $this->assertInternalType('array', $result);
65        $this->assertCount(3, $result);
66        $this->assertInternalType('resource', $result[0]);
67        $this->assertInternalType('array', $result[1]);
68        $this->assertSame($stream, $result[2]);
69        curl_close($result[0]);
70
71        $this->assertEquals('PUT', $_SERVER['_curl'][CURLOPT_CUSTOMREQUEST]);
72        $this->assertEquals(
73            'http://http://127.0.0.1:8125/',
74            $_SERVER['_curl'][CURLOPT_URL]
75        );
76        // Sends via post fields when the request is small enough
77        $this->assertEquals('testing', $_SERVER['_curl'][CURLOPT_POSTFIELDS]);
78        $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_RETURNTRANSFER]);
79        $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_HEADER]);
80        $this->assertEquals(150, $_SERVER['_curl'][CURLOPT_CONNECTTIMEOUT]);
81        $this->assertInstanceOf('Closure', $_SERVER['_curl'][CURLOPT_HEADERFUNCTION]);
82
83        if (defined('CURLOPT_PROTOCOLS')) {
84            $this->assertEquals(
85                CURLPROTO_HTTP | CURLPROTO_HTTPS,
86                $_SERVER['_curl'][CURLOPT_PROTOCOLS]
87            );
88        }
89
90        $this->assertContains('Expect:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]);
91        $this->assertContains('Accept:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]);
92        $this->assertContains('Content-Type:', $_SERVER['_curl'][CURLOPT_HTTPHEADER]);
93        $this->assertContains('Hi:  123', $_SERVER['_curl'][CURLOPT_HTTPHEADER]);
94        $this->assertContains('host: http://127.0.0.1:8125/', $_SERVER['_curl'][CURLOPT_HTTPHEADER]);
95    }
96
97    public function testSendsHeadRequests()
98    {
99        Server::flush();
100        Server::enqueue([['status' => 200]]);
101        $a = new CurlMultiHandler();
102        $response = $a([
103            'http_method' => 'HEAD',
104            'headers' => ['host' => [Server::$host]],
105        ]);
106        $response->wait();
107        $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_NOBODY]);
108        $checks = [CURLOPT_WRITEFUNCTION, CURLOPT_READFUNCTION, CURLOPT_FILE, CURLOPT_INFILE];
109        foreach ($checks as $check) {
110            $this->assertArrayNotHasKey($check, $_SERVER['_curl']);
111        }
112        $this->assertEquals('HEAD', Server::received()[0]['http_method']);
113    }
114
115    public function testCanAddCustomCurlOptions()
116    {
117        Server::flush();
118        Server::enqueue([['status' => 200]]);
119        $a = new CurlMultiHandler();
120        $a([
121            'http_method' => 'GET',
122            'headers'     => ['host' => [Server::$host]],
123            'client'      => ['curl' => [CURLOPT_LOW_SPEED_LIMIT => 10]],
124        ]);
125        $this->assertEquals(10, $_SERVER['_curl'][CURLOPT_LOW_SPEED_LIMIT]);
126    }
127
128    /**
129     * @expectedException \InvalidArgumentException
130     * @expectedExceptionMessage SSL CA bundle not found: /does/not/exist
131     */
132    public function testValidatesVerify()
133    {
134        $f = new CurlFactory();
135        $f([
136            'http_method' => 'GET',
137            'headers' => ['host' => ['foo.com']],
138            'client' => ['verify' => '/does/not/exist'],
139        ]);
140    }
141
142    public function testCanSetVerifyToFile()
143    {
144        $f = new CurlFactory();
145        $f([
146            'http_method' => 'GET',
147            'headers' => ['host' => ['foo.com']],
148            'client' => ['verify' => __FILE__],
149        ]);
150        $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_CAINFO]);
151        $this->assertEquals(2, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]);
152        $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]);
153    }
154
155    public function testAddsVerifyAsTrue()
156    {
157        $f = new CurlFactory();
158        $f([
159            'http_method' => 'GET',
160            'headers' => ['host' => ['foo.com']],
161            'client' => ['verify' => true],
162        ]);
163        $this->assertEquals(2, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]);
164        $this->assertEquals(true, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]);
165        $this->assertArrayNotHasKey(CURLOPT_CAINFO, $_SERVER['_curl']);
166    }
167
168    public function testCanDisableVerify()
169    {
170        $f = new CurlFactory();
171        $f([
172            'http_method' => 'GET',
173            'headers' => ['host' => ['foo.com']],
174            'client' => ['verify' => false],
175        ]);
176        $this->assertEquals(0, $_SERVER['_curl'][CURLOPT_SSL_VERIFYHOST]);
177        $this->assertEquals(false, $_SERVER['_curl'][CURLOPT_SSL_VERIFYPEER]);
178    }
179
180    public function testAddsProxy()
181    {
182        $f = new CurlFactory();
183        $f([
184            'http_method' => 'GET',
185            'headers' => ['host' => ['foo.com']],
186            'client' => ['proxy' => 'http://bar.com'],
187        ]);
188        $this->assertEquals('http://bar.com', $_SERVER['_curl'][CURLOPT_PROXY]);
189    }
190
191    public function testAddsViaScheme()
192    {
193        $f = new CurlFactory();
194        $f([
195            'http_method' => 'GET',
196            'scheme' => 'http',
197            'headers' => ['host' => ['foo.com']],
198            'client' => [
199                'proxy' => ['http' => 'http://bar.com', 'https' => 'https://t'],
200            ],
201        ]);
202        $this->assertEquals('http://bar.com', $_SERVER['_curl'][CURLOPT_PROXY]);
203    }
204
205    /**
206     * @expectedException \InvalidArgumentException
207     * @expectedExceptionMessage SSL private key not found: /does/not/exist
208     */
209    public function testValidatesSslKey()
210    {
211        $f = new CurlFactory();
212        $f([
213            'http_method' => 'GET',
214            'headers' => ['host' => ['foo.com']],
215            'client' => ['ssl_key' => '/does/not/exist'],
216        ]);
217    }
218
219    public function testAddsSslKey()
220    {
221        $f = new CurlFactory();
222        $f([
223            'http_method' => 'GET',
224            'headers' => ['host' => ['foo.com']],
225            'client' => ['ssl_key' => __FILE__],
226        ]);
227        $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLKEY]);
228    }
229
230    public function testAddsSslKeyWithPassword()
231    {
232        $f = new CurlFactory();
233        $f([
234            'http_method' => 'GET',
235            'headers' => ['host' => ['foo.com']],
236            'client' => ['ssl_key' => [__FILE__, 'test']],
237        ]);
238        $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLKEY]);
239        $this->assertEquals('test', $_SERVER['_curl'][CURLOPT_SSLKEYPASSWD]);
240    }
241
242    /**
243     * @expectedException \InvalidArgumentException
244     * @expectedExceptionMessage SSL certificate not found: /does/not/exist
245     */
246    public function testValidatesCert()
247    {
248        $f = new CurlFactory();
249        $f([
250            'http_method' => 'GET',
251            'headers' => ['host' => ['foo.com']],
252            'client' => ['cert' => '/does/not/exist'],
253        ]);
254    }
255
256    public function testAddsCert()
257    {
258        $f = new CurlFactory();
259        $f([
260            'http_method' => 'GET',
261            'headers' => ['host' => ['foo.com']],
262            'client' => ['cert' => __FILE__],
263        ]);
264        $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLCERT]);
265    }
266
267    public function testAddsCertWithPassword()
268    {
269        $f = new CurlFactory();
270        $f([
271            'http_method' => 'GET',
272            'headers' => ['host' => ['foo.com']],
273            'client' => ['cert' => [__FILE__, 'test']],
274        ]);
275        $this->assertEquals(__FILE__, $_SERVER['_curl'][CURLOPT_SSLCERT]);
276        $this->assertEquals('test', $_SERVER['_curl'][CURLOPT_SSLCERTPASSWD]);
277    }
278
279    /**
280     * @expectedException \InvalidArgumentException
281     * @expectedExceptionMessage progress client option must be callable
282     */
283    public function testValidatesProgress()
284    {
285        $f = new CurlFactory();
286        $f([
287            'http_method' => 'GET',
288            'headers' => ['host' => ['foo.com']],
289            'client' => ['progress' => 'foo'],
290        ]);
291    }
292
293    public function testEmitsDebugInfoToStream()
294    {
295        $res = fopen('php://memory', 'r+');
296        Server::flush();
297        Server::enqueue([['status' => 200]]);
298        $a = new CurlMultiHandler();
299        $response = $a([
300            'http_method' => 'HEAD',
301            'headers'     => ['host' => [Server::$host]],
302            'client'      => ['debug' => $res],
303        ]);
304        $response->wait();
305        rewind($res);
306        $output = str_replace("\r", '', stream_get_contents($res));
307        $this->assertContains(
308            "> HEAD / HTTP/1.1\nhost: 127.0.0.1:8125\n\n",
309            $output
310        );
311        $this->assertContains("< HTTP/1.1 200", $output);
312        fclose($res);
313    }
314
315    public function testEmitsProgressToFunction()
316    {
317        Server::flush();
318        Server::enqueue([['status' => 200]]);
319        $a = new CurlMultiHandler();
320        $called = [];
321        $response = $a([
322            'http_method' => 'HEAD',
323            'headers'     => ['host' => [Server::$host]],
324            'client'      => [
325                'progress' => function () use (&$called) {
326                    $called[] = func_get_args();
327                },
328            ],
329        ]);
330        $response->wait();
331        $this->assertNotEmpty($called);
332        foreach ($called as $call) {
333            $this->assertCount(4, $call);
334        }
335    }
336
337    private function addDecodeResponse($withEncoding = true)
338    {
339        $content = gzencode('test');
340        $response  = [
341            'status'  => 200,
342            'reason'  => 'OK',
343            'headers' => ['Content-Length' => [strlen($content)]],
344            'body'    => $content,
345        ];
346
347        if ($withEncoding) {
348            $response['headers']['Content-Encoding'] = ['gzip'];
349        }
350
351        Server::flush();
352        Server::enqueue([$response]);
353
354        return $content;
355    }
356
357    public function testDecodesGzippedResponses()
358    {
359        $this->addDecodeResponse();
360        $handler = new CurlMultiHandler();
361        $response = $handler([
362            'http_method' => 'GET',
363            'headers'     => ['host' => [Server::$host]],
364            'client'      => ['decode_content' => true],
365        ]);
366        $response->wait();
367        $this->assertEquals('test', Core::body($response));
368        $this->assertEquals('', $_SERVER['_curl'][CURLOPT_ENCODING]);
369        $sent = Server::received()[0];
370        $this->assertNull(Core::header($sent, 'Accept-Encoding'));
371    }
372
373    public function testDecodesGzippedResponsesWithHeader()
374    {
375        $this->addDecodeResponse();
376        $handler = new CurlMultiHandler();
377        $response = $handler([
378            'http_method' => 'GET',
379            'headers'     => [
380                'host'            => [Server::$host],
381                'Accept-Encoding' => ['gzip'],
382            ],
383            'client' => ['decode_content' => true],
384        ]);
385        $response->wait();
386        $this->assertEquals('gzip', $_SERVER['_curl'][CURLOPT_ENCODING]);
387        $sent = Server::received()[0];
388        $this->assertEquals('gzip', Core::header($sent, 'Accept-Encoding'));
389        $this->assertEquals('test', Core::body($response));
390    }
391
392    public function testDoesNotForceDecode()
393    {
394        $content = $this->addDecodeResponse();
395        $handler = new CurlMultiHandler();
396        $response = $handler([
397            'http_method' => 'GET',
398            'headers'     => ['host' => [Server::$host]],
399            'client'      => ['decode_content' => false],
400        ]);
401        $response->wait();
402        $sent = Server::received()[0];
403        $this->assertNull(Core::header($sent, 'Accept-Encoding'));
404        $this->assertEquals($content, Core::body($response));
405    }
406
407    public function testProtocolVersion()
408    {
409        Server::flush();
410        Server::enqueue([['status' => 200]]);
411        $a = new CurlMultiHandler();
412        $a([
413            'http_method' => 'GET',
414            'headers'     => ['host' => [Server::$host]],
415            'version'     => 1.0,
416        ]);
417        $this->assertEquals(CURL_HTTP_VERSION_1_0, $_SERVER['_curl'][CURLOPT_HTTP_VERSION]);
418    }
419
420    /**
421     * @expectedException \InvalidArgumentException
422     */
423    public function testValidatesSaveTo()
424    {
425        $handler = new CurlMultiHandler();
426        $handler([
427            'http_method' => 'GET',
428            'headers'     => ['host' => [Server::$host]],
429            'client'      => ['save_to' => true],
430        ]);
431    }
432
433    public function testSavesToStream()
434    {
435        $stream = fopen('php://memory', 'r+');
436        $this->addDecodeResponse();
437        $handler = new CurlMultiHandler();
438        $response = $handler([
439            'http_method' => 'GET',
440            'headers'     => ['host' => [Server::$host]],
441            'client' => [
442                'decode_content' => true,
443                'save_to' => $stream,
444            ],
445        ]);
446        $response->wait();
447        rewind($stream);
448        $this->assertEquals('test', stream_get_contents($stream));
449    }
450
451    public function testSavesToGuzzleStream()
452    {
453        $stream = Stream::factory();
454        $this->addDecodeResponse();
455        $handler = new CurlMultiHandler();
456        $response = $handler([
457            'http_method' => 'GET',
458            'headers'     => ['host' => [Server::$host]],
459            'client' => [
460                'decode_content' => true,
461                'save_to'        => $stream,
462            ],
463        ]);
464        $response->wait();
465        $this->assertEquals('test', (string) $stream);
466    }
467
468    public function testSavesToFileOnDisk()
469    {
470        $tmpfile = tempnam(sys_get_temp_dir(), 'testfile');
471        $this->addDecodeResponse();
472        $handler = new CurlMultiHandler();
473        $response = $handler([
474            'http_method' => 'GET',
475            'headers'     => ['host' => [Server::$host]],
476            'client' => [
477                'decode_content' => true,
478                'save_to'        => $tmpfile,
479            ],
480        ]);
481        $response->wait();
482        $this->assertEquals('test', file_get_contents($tmpfile));
483        unlink($tmpfile);
484    }
485
486    /**
487     * @expectedException \InvalidArgumentException
488     */
489    public function testValidatesBody()
490    {
491        $handler = new CurlMultiHandler();
492        $handler([
493            'http_method' => 'GET',
494            'headers'     => ['host' => [Server::$host]],
495            'body'        => false,
496        ]);
497    }
498
499    public function testAddsLargePayloadFromStreamWithNoSizeUsingChunked()
500    {
501        $stream = Stream::factory('foo');
502        $stream = FnStream::decorate($stream, [
503            'getSize' => function () {
504                return null;
505            }
506        ]);
507        $this->addDecodeResponse();
508        $handler = new CurlMultiHandler();
509        $response = $handler([
510            'http_method' => 'GET',
511            'headers'     => ['host' => [Server::$host]],
512            'body'        => $stream,
513        ]);
514        $response->wait();
515        $sent = Server::received()[0];
516        $this->assertEquals('chunked', Core::header($sent, 'Transfer-Encoding'));
517        $this->assertNull(Core::header($sent, 'Content-Length'));
518        $this->assertEquals('foo', $sent['body']);
519    }
520
521    public function testAddsPayloadFromIterator()
522    {
523        $iter = new \ArrayIterator(['f', 'o', 'o']);
524        $this->addDecodeResponse();
525        $handler = new CurlMultiHandler();
526        $response = $handler([
527            'http_method' => 'GET',
528            'headers'     => ['host' => [Server::$host]],
529            'body'        => $iter,
530        ]);
531        $response->wait();
532        $sent = Server::received()[0];
533        $this->assertEquals('chunked', Core::header($sent, 'Transfer-Encoding'));
534        $this->assertNull(Core::header($sent, 'Content-Length'));
535        $this->assertEquals('foo', $sent['body']);
536    }
537
538    public function testAddsPayloadFromResource()
539    {
540        $res = fopen('php://memory', 'r+');
541        $data = str_repeat('.', 1000000);
542        fwrite($res, $data);
543        rewind($res);
544        $this->addDecodeResponse();
545        $handler = new CurlMultiHandler();
546        $response = $handler([
547            'http_method' => 'GET',
548            'headers'     => [
549                'host'           => [Server::$host],
550                'content-length' => [1000000],
551            ],
552            'body'        => $res,
553        ]);
554        $response->wait();
555        $sent = Server::received()[0];
556        $this->assertNull(Core::header($sent, 'Transfer-Encoding'));
557        $this->assertEquals(1000000, Core::header($sent, 'Content-Length'));
558        $this->assertEquals($data, $sent['body']);
559    }
560
561    public function testAddsContentLengthFromStream()
562    {
563        $stream = Stream::factory('foo');
564        $this->addDecodeResponse();
565        $handler = new CurlMultiHandler();
566        $response = $handler([
567            'http_method' => 'GET',
568            'headers'     => ['host' => [Server::$host]],
569            'body'        => $stream,
570        ]);
571        $response->wait();
572        $sent = Server::received()[0];
573        $this->assertEquals(3, Core::header($sent, 'Content-Length'));
574        $this->assertNull(Core::header($sent, 'Transfer-Encoding'));
575        $this->assertEquals('foo', $sent['body']);
576    }
577
578    public function testDoesNotAddMultipleContentLengthHeaders()
579    {
580        $this->addDecodeResponse();
581        $handler = new CurlMultiHandler();
582        $response = $handler([
583            'http_method' => 'GET',
584            'headers'     => [
585                'host'           => [Server::$host],
586                'content-length' => [3],
587            ],
588            'body' => 'foo',
589        ]);
590        $response->wait();
591        $sent = Server::received()[0];
592        $this->assertEquals(3, Core::header($sent, 'Content-Length'));
593        $this->assertNull(Core::header($sent, 'Transfer-Encoding'));
594        $this->assertEquals('foo', $sent['body']);
595    }
596
597    public function testSendsPostWithNoBodyOrDefaultContentType()
598    {
599        Server::flush();
600        Server::enqueue([['status' => 200]]);
601        $handler = new CurlMultiHandler();
602        $response = $handler([
603            'http_method' => 'POST',
604            'uri'         => '/',
605            'headers'     => ['host' => [Server::$host]],
606        ]);
607        $response->wait();
608        $received = Server::received()[0];
609        $this->assertEquals('POST', $received['http_method']);
610        $this->assertNull(Core::header($received, 'content-type'));
611        $this->assertSame('0', Core::firstHeader($received, 'content-length'));
612    }
613
614    public function testParseProtocolVersion()
615    {
616        $res = CurlFactory::createResponse(
617            function () {},
618            [],
619            ['curl' => ['errno' => null]],
620            ['HTTP/1.1 200 Ok'],
621            null
622        );
623
624        $this->assertSame('1.1', $res['version']);
625    }
626
627    public function testFailsWhenNoResponseAndNoBody()
628    {
629        $res = CurlFactory::createResponse(function () {}, [], [], [], null);
630        $this->assertInstanceOf('GuzzleHttp\Ring\Exception\RingException', $res['error']);
631        $this->assertContains(
632            'No response was received for a request with no body',
633            $res['error']->getMessage()
634        );
635    }
636
637    public function testFailsWhenCannotRewindRetry()
638    {
639        $res = CurlFactory::createResponse(function () {}, [
640            'body' => new NoSeekStream(Stream::factory('foo'))
641        ], [], [], null);
642        $this->assertInstanceOf('GuzzleHttp\Ring\Exception\RingException', $res['error']);
643        $this->assertContains(
644            'rewind the request body failed',
645            $res['error']->getMessage()
646        );
647    }
648
649    public function testRetriesWhenBodyCanBeRewound()
650    {
651        $callHandler = $called = false;
652        $res = CurlFactory::createResponse(function () use (&$callHandler) {
653            $callHandler = true;
654            return ['status' => 200];
655        }, [
656            'body' => FnStream::decorate(Stream::factory('test'), [
657                'seek' => function () use (&$called) {
658                    $called = true;
659                    return true;
660                }
661            ])
662        ], [], [], null);
663
664        $this->assertTrue($callHandler);
665        $this->assertTrue($called);
666        $this->assertEquals('200', $res['status']);
667    }
668
669    public function testFailsWhenRetryMoreThanThreeTimes()
670    {
671        $call = 0;
672        $mock = new MockHandler(function (array $request) use (&$mock, &$call) {
673            $call++;
674            return CurlFactory::createResponse($mock, $request, [], [], null);
675        });
676        $response = $mock([
677            'http_method' => 'GET',
678            'body'        => 'test',
679        ]);
680        $this->assertEquals(3, $call);
681        $this->assertArrayHasKey('error', $response);
682        $this->assertContains(
683            'The cURL request was retried 3 times',
684            $response['error']->getMessage()
685        );
686    }
687
688    public function testHandles100Continue()
689    {
690        Server::flush();
691        Server::enqueue([
692            [
693                'status' => '200',
694                'reason' => 'OK',
695                'headers' => [
696                    'Test' => ['Hello'],
697                    'Content-Length' => ['4'],
698                ],
699                'body' => 'test',
700            ],
701        ]);
702
703        $request = [
704            'http_method' => 'PUT',
705            'headers'     => [
706                'Host'   => [Server::$host],
707                'Expect' => ['100-Continue'],
708            ],
709            'body'        => 'test',
710        ];
711
712        $handler = new CurlMultiHandler();
713        $response = $handler($request)->wait();
714        $this->assertEquals(200, $response['status']);
715        $this->assertEquals('OK', $response['reason']);
716        $this->assertEquals(['Hello'], $response['headers']['Test']);
717        $this->assertEquals(['4'], $response['headers']['Content-Length']);
718        $this->assertEquals('test', Core::body($response));
719    }
720
721    public function testCreatesConnectException()
722    {
723        $m = new \ReflectionMethod('GuzzleHttp\Ring\Client\CurlFactory', 'createErrorResponse');
724        $m->setAccessible(true);
725        $response = $m->invoke(
726            null,
727            function () {},
728            [],
729            [
730                'err_message' => 'foo',
731                'curl' => [
732                    'errno' => CURLE_COULDNT_CONNECT,
733                ]
734            ]
735        );
736        $this->assertInstanceOf('GuzzleHttp\Ring\Exception\ConnectException', $response['error']);
737    }
738
739    public function testParsesLastResponseOnly()
740    {
741        $response1  = [
742            'status'  => 301,
743            'headers' => [
744                'Content-Length' => ['0'],
745                'Location' => ['/foo']
746            ]
747        ];
748
749        $response2 = [
750            'status'  => 200,
751            'headers' => [
752                'Content-Length' => ['0'],
753                'Foo' => ['bar']
754            ]
755        ];
756
757        Server::flush();
758        Server::enqueue([$response1, $response2]);
759
760        $a = new CurlMultiHandler();
761        $response = $a([
762            'http_method' => 'GET',
763            'headers'     => ['Host'   => [Server::$host]],
764            'client' => [
765                'curl' => [
766                    CURLOPT_FOLLOWLOCATION => true
767                ]
768            ]
769        ])->wait();
770
771        $this->assertEquals(1, $response['transfer_stats']['redirect_count']);
772        $this->assertEquals('http://127.0.0.1:8125/foo', $response['effective_url']);
773        $this->assertEquals(['bar'], $response['headers']['Foo']);
774        $this->assertEquals(200, $response['status']);
775        $this->assertFalse(Core::hasHeader($response, 'Location'));
776    }
777
778    public function testMaintainsMultiHeaderOrder()
779    {
780        Server::flush();
781        Server::enqueue([
782            [
783                'status'  => 200,
784                'headers' => [
785                    'Content-Length' => ['0'],
786                    'Foo' => ['a', 'b'],
787                    'foo' => ['c', 'd'],
788                ]
789            ]
790        ]);
791
792        $a = new CurlMultiHandler();
793        $response = $a([
794            'http_method' => 'GET',
795            'headers'     => ['Host'   => [Server::$host]]
796        ])->wait();
797
798        $this->assertEquals(
799            ['a', 'b', 'c', 'd'],
800            Core::headerLines($response, 'Foo')
801        );
802    }
803
804    /**
805     * @expectedException \RuntimeException
806     * @expectedExceptionMessage Directory /path/to/does/not does not exist for save_to value of /path/to/does/not/exist.txt
807     */
808    public function testThrowsWhenDirNotFound()
809    {
810        $request = [
811            'http_method' => 'GET',
812            'headers' => ['host' => [Server::$url]],
813            'client' => ['save_to' => '/path/to/does/not/exist.txt'],
814        ];
815
816        $f = new CurlFactory();
817        $f($request);
818    }
819}
820
821}
822