Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 204
0.00% covered (danger)
0.00%
0 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 3
AblyRestTest
0.00% covered (danger)
0.00%
0 / 189
0.00% covered (danger)
0.00%
0 / 21
702
0.00% covered (danger)
0.00%
0 / 1
 testInitLibWithKeyString
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 testInitLibWithKeyOption
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 testInitLibWithTokenString
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 testInitLibWithTokenOption
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 testInitLibWithTokenDetailsOption
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 testInitLibWithSpecifiedHost
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 testInitLibWithSpecifiedPort
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
2
 testInitLibWithSpecifiedEnv
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 testInitLibWithSpecifiedEnvHost
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 testTLSDefaultIsTrue
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 testTLSCanBeFalse
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 testTLSExplicitTrue
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 testFallbackHosts
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 testCustomHostAndFallbacks
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
6
 testFallbackHosts400
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 testNoFallbackOnCustomHost
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 testFallbackHostsFailFirst3
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 testMaxRetryCount
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 testTimeAndAccuracy
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 testTimeFailsWithInvalidHost
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 testHttpTimeout
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
HttpMockInitTest
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 request
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
HttpMockInitTestTimeout
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 1
 request
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2namespace tests;
3use Ably\AblyRest;
4use Ably\Http;
5use Ably\Exceptions\AblyRequestException;
6use Ably\Models\ClientOptions;
7use Ably\Models\TokenDetails;
8
9require_once __DIR__ . '/factories/TestApp.php';
10
11class AblyRestTest extends \PHPUnit_Framework_TestCase {
12
13    /**
14     * Init library with a key string
15     */
16    public function testInitLibWithKeyString() {
17        $key = 'fake.key:veryFake';
18        $ably = new AblyRest( $key );
19        $this->assertTrue( $ably->auth->isUsingBasicAuth(), 'Expected basic auth to be used' );
20    }
21
22    /**
23     * Init library with a key in options
24     */
25    public function testInitLibWithKeyOption() {
26        $key = 'fake.key:veryFake';
27        $ably = new AblyRest( ['key' => $key ] );
28        $this->assertTrue( $ably->auth->isUsingBasicAuth(), 'Expected basic auth to be used' );
29    }
30
31    /**
32     * Init library with a token string
33     */
34    public function testInitLibWithTokenString() {
35        $token = 'fake_token'; // token string never contains a colon
36        $ably = new AblyRest( $token );
37        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
38    }
39
40    /**
41     * Init library with a token string in options
42     */
43    public function testInitLibWithTokenOption() {
44        $ably = new AblyRest( [
45            'token' => "this_is_not_really_a_token",
46        ] );
47
48        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
49    }
50
51    /**
52     * Init library with a tokenDetails in options
53     */
54    public function testInitLibWithTokenDetailsOption() {
55        $ably = new AblyRest( [
56            'tokenDetails' => new TokenDetails( "this_is_not_really_a_token" ),
57        ] );
58
59        $this->assertFalse( $ably->auth->isUsingBasicAuth(), 'Expected token auth to be used' );
60    }
61
62    /**
63     * Init library with a specified host
64     */
65    public function testInitLibWithSpecifiedHost() {
66        $opts = [
67            'key' => 'fake.key:veryFake',
68            'restHost'  => 'some.other.host',
69            'httpClass' => 'tests\HttpMockInitTest',
70        ];
71        $ably = new AblyRest( $opts );
72        $ably->time(); // make a request
73        $this->assertRegExp( '/^https?:\/\/some\.other\.host/', $ably->http->lastUrl, 'Unexpected host mismatch' );
74    }
75
76    /**
77     * Init library with a specified port
78     */
79    public function testInitLibWithSpecifiedPort() {
80        $opts = [
81            'key' => 'fake.key:veryFake',
82            'restHost'  => 'some.other.host',
83            'tlsPort' => 999,
84            'httpClass' => 'tests\HttpMockInitTest',
85        ];
86        $ably = new AblyRest( $opts );
87        $ably->time(); // make a request
88        $this->assertContains( 'https://' . $opts['restHost'] . ':' . $opts['tlsPort'], $ably->http->lastUrl, 'Unexpected host/port mismatch' );
89
90        $opts = [
91            'token' => 'fakeToken',
92            'restHost'  => 'some.other.host',
93            'port' => 999,
94            'tls' => false,
95            'httpClass' => 'tests\HttpMockInitTest',
96        ];
97        $ably = new AblyRest( $opts );
98        $ably->time(); // make a request
99        $this->assertContains( 'http://' . $opts['restHost'] . ':' . $opts['port'], $ably->http->lastUrl, 'Unexpected host/port mismatch' );
100    }
101
102    /**
103     * Init library with specified environment
104     */
105    public function testInitLibWithSpecifiedEnv() {
106        $ably = new AblyRest( [
107            'key' => 'fake.key:veryFake',
108            'environment'  => 'sandbox',
109            'httpClass' => 'tests\HttpMockInitTest',
110        ] );
111        $ably->time(); // make a request
112        $this->assertRegExp( '/^https?:\/\/sandbox-rest\.ably\.io\//', $ably->http->lastUrl, 'Unexpected host mismatch' );
113    }
114
115    /**
116     * Init library with specified environment AND host
117     */
118    public function testInitLibWithSpecifiedEnvHost() {
119        $ably = new AblyRest( [
120            'key' => 'fake.key:veryFake',
121            'restHost'  => 'some.other.host',
122            'environment'  => 'sandbox',
123            'httpClass' => 'tests\HttpMockInitTest',
124        ] );
125        $ably->time(); // make a request
126        $this->assertRegExp( '/^https?:\/\/sandbox-some\.other\.host\//', $ably->http->lastUrl, 'Unexpected host mismatch' );
127    }
128
129    /**
130     * Verify encrypted defaults to true, makes a request to https://rest.ably.io/...
131     */
132    public function testTLSDefaultIsTrue() {
133        $opts = [
134            'key' => 'fake.key:veryFake',
135            'httpClass' => 'tests\HttpMockInitTest',
136        ];
137        $ably = new AblyRest( $opts );
138        $ably->time(); // make a request
139        $this->assertRegExp( '/^https:\/\/rest\.ably\.io/', $ably->http->lastUrl, 'Unexpected scheme/url mismatch' );
140    }
141
142    /**
143     * Verify encrypted can be set to false, makes a request to http://rest.ably.io/...
144     */
145    public function testTLSCanBeFalse() {
146        $opts = [
147            'token' => 'fake.token',
148            'httpClass' => 'tests\HttpMockInitTest',
149            'tls' => false,
150        ];
151        $ably = new AblyRest( $opts );
152        $ably->time(); // make a request
153        $this->assertRegExp( '/^http:\/\/rest\.ably\.io/', $ably->http->lastUrl, 'Unexpected scheme/url mismatch' );
154    }
155
156    /**
157     * Verify that connection is encrypted when set to true explicitly, makes a request to https://rest.ably.io/...
158     */
159    public function testTLSExplicitTrue() {
160        $opts = [
161            'token' => 'fake.token',
162            'httpClass' => 'tests\HttpMockInitTest',
163            'tls' => true,
164        ];
165        $ably = new AblyRest( $opts );
166        $ably->time(); // make a request
167        $this->assertRegExp( '/^https:\/\/rest\.ably\.io/', $ably->http->lastUrl, 'Unexpected scheme/url mismatch' );
168    }
169
170    /**
171     * Verify that fallback hosts are working and used in correct order
172     */
173    public function testFallbackHosts() {
174        $defaultOpts = new ClientOptions();
175        $hostWithFallbacks = array_merge( [ $defaultOpts->restHost ], $defaultOpts->fallbackHosts );
176        $hostWithFallbacksSorted = $hostWithFallbacks; // copied by value
177        sort($hostWithFallbacksSorted);
178
179        $opts = [
180            'key' => 'fake.key:veryFake',
181            'httpClass' => 'tests\HttpMockInitTestTimeout',
182            'httpMaxRetryCount' => 5,
183        ];
184        $ably = new AblyRest( $opts );
185        try {
186            $ably->time(); // make a request
187            $this->fail('Expected the request to fail');
188        } catch(AblyRequestException $e) {
189            $this->assertEquals( $hostWithFallbacks[0], $ably->http->failedHosts[0], 'Expected to try restHost first' );
190            $this->assertNotEquals( $hostWithFallbacks, $ably->http->failedHosts, 'Expected to have fallback hosts randomized' );
191
192            $failedHostsSorted = $ably->http->failedHosts; // copied by value;
193            sort($failedHostsSorted);
194            $this->assertEquals( $hostWithFallbacksSorted, $failedHostsSorted, 'Expected to have tried all the fallback hosts' );
195        }
196    }
197
198    /**
199     * Verify that custom restHost and custom fallbackHosts are working
200     */
201    public function testCustomHostAndFallbacks() {
202        $defaultOpts = new ClientOptions([
203            'restHost' => 'rest.custom.com',
204            'fallbackHosts' => [
205                'first-fallback.custom.com',
206                'second-fallback.custom.com',
207                'third-fallback.custom.com',
208            ],
209        ]);
210        $hostWithFallbacks = array_merge( [ $defaultOpts->restHost ], $defaultOpts->fallbackHosts );
211        $hostWithFallbacksSorted = $hostWithFallbacks; // copied by value
212        sort($hostWithFallbacksSorted);
213
214        $opts = array_merge ( $defaultOpts->toArray(), [
215            'key' => 'fake.key:veryFake',
216            'httpClass' => 'tests\HttpMockInitTestTimeout',
217            'httpMaxRetryCount' => 3,
218        ] );
219        $ably = new AblyRest( $opts );
220        try {
221            $ably->time(); // make a request
222            $this->fail('Expected the request to fail');
223        } catch(AblyRequestException $e) {
224           $this->assertEquals( $hostWithFallbacks[0], $ably->http->failedHosts[0], 'Expected to try restHost first' );
225            // $this->assertNotEquals( $hostWithFallbacks, $ably->http->failedHosts, 'Expected to have fallback hosts randomized' ); // this may fail when randomized order matches the original order
226            
227           $failedHostsSorted = $ably->http->failedHosts; // copied by value;
228           sort($failedHostsSorted);
229           $this->assertEquals( $hostWithFallbacksSorted, $failedHostsSorted, 'Expected to have tried all the fallback hosts' );
230        }
231    }
232
233    /**
234     * Verify that fallback hosts are not called on a 400 error
235     */
236    public function testFallbackHosts400() {
237
238        $opts = [
239            'key' => 'fake.key:veryFake',
240            'httpClass' => 'tests\HttpMockInitTestTimeout',
241        ];
242
243        $ably = new AblyRest( $opts );
244        $ably->http->httpErrorCode = 401;
245        $ably->http->errorCode = 40101; // auth error
246
247        try {
248            $ably->time(); // make a request
249            $this->fail('Expected the request to fail');
250        } catch(AblyRequestException $e) {
251            $this->assertEquals( [ 'rest.ably.io' ], $ably->http->failedHosts, 'Expected to have tried only the default host' );
252        }
253    }
254
255    /**
256     * Verify that default fallback hosts are NOT used when using a custom host
257     */
258    public function testNoFallbackOnCustomHost() {
259
260        // reuse default options so that fallback host order is not randomized again
261        $opts = [
262            'key' => 'fake.key:veryFake',
263            'httpClass' => 'tests\HttpMockInitTestTimeout',
264            'restHost' => 'custom.host.com',
265        ];
266        $ably = new AblyRest( $opts );
267        try {
268            $ably->time(); // make a request
269            $this->fail('Expected the request to fail');
270        } catch(AblyRequestException $e) {
271            $this->assertEquals( [ 'custom.host.com' ], $ably->http->failedHosts, 'Expected to have tried only the custom host' );
272        }
273    }
274
275    /**
276     * Verify that fallback hosts are working - first 3 fail, 4th works
277     */
278    public function testFallbackHostsFailFirst3() {
279        $opts = [
280            'key' => 'fake.key:veryFake',
281            'httpClass' => 'tests\HttpMockInitTestTimeout',
282            'httpMaxRetryCount' => 5,
283        ];
284        $ably = new AblyRest( $opts );
285        $ably->http->failAttempts = 3;
286        $data = $ably->time(); // make a request
287        
288        $this->assertEquals( 999999, $data, 'Expected to receive test data' );
289        $this->assertEquals( 3, count( $ably->http->failedHosts ), 'Expected 3 hosts to fail' );
290    }
291
292    /**
293     * Verify that the httpMaxRetryCount option is honored
294     */
295    public function testMaxRetryCount() {
296        $opts = [
297            'key' => 'fake.key:veryFake',
298            'httpClass' => 'tests\HttpMockInitTestTimeout',
299            'httpMaxRetryCount' => 2,
300        ];
301
302        $ably = new AblyRest( $opts );
303        try {
304            $ably->time(); // make a request
305            $this->fail('Expected the request to fail');
306        } catch(AblyRequestException $e) {
307            $this->assertEquals( 3, count($ably->http->failedHosts), 'Expected to have tried main host and 2 fallback hosts' );
308        }
309    }
310
311    /**
312     * Verify accuracy of time (to within 2 seconds of actual time)
313     */
314    public function testTimeAndAccuracy() {
315        $opts = [
316            'key' => 'fake.key:veryFake',
317        ];
318        $ably = new AblyRest( $opts );
319
320        $reportedTime = intval($ably->time());
321        $actualTime = intval(microtime(true)*1000);
322
323        $this->assertTrue( abs($reportedTime - $actualTime) < 2000,
324            'The time difference was larger than 2000ms: ' . ($reportedTime - $actualTime) .'. Please check your system clock.' );
325    }
326
327    /**
328     * Verify that time fails without valid host
329     */
330    public function testTimeFailsWithInvalidHost() {
331        $ablyInvalidHost = new AblyRest( [
332            'key' => 'fake.key:veryFake',
333            'restHost' => 'this.host.does.not.exist',
334        ]);
335
336        $this->setExpectedException('Ably\Exceptions\AblyRequestException');
337        $reportedTime = $ablyInvalidHost->time();
338    }
339
340    /**
341     * Verify that custom request timeout works.
342     * Connection/open timeout not reliably testable.
343     */
344    public function testHttpTimeout() {
345        $ably = new AblyRest( [
346            'key' => 'fake.key:veryFake',
347        ]);
348
349        $ablyTimeout = new AblyRest( [
350            'key' => 'fake.key:veryFake',
351            'httpRequestTimeout' => 50, // 50 ms
352        ]);
353
354        $ably->http->get('https://cdn.ably.io/lib/ably.js'); // should work
355        $this->setExpectedException('Ably\Exceptions\AblyRequestException', '', 50003);
356        $ablyTimeout->http->get('https://cdn.ably.io/lib/ably.js'); // guaranteed to take more than 50 ms
357    }
358}
359
360
361class HttpMockInitTest extends Http {
362    public $lastUrl;
363    
364    public function request($method, $url, $headers = [], $params = []) {
365        $this->lastUrl = $url;
366
367        // mock response to /time
368        return [
369            'headers' => '',
370            'body' => [ round( microtime( true ) * 1000 ), 0 ]
371        ];
372    }
373}
374
375
376class HttpMockInitTestTimeout extends Http {
377    public $failedHosts = [];
378    public $failAttempts = 100; // number of attempts to time out before starting to return data
379    public $httpErrorCode = 500;
380    public $errorCode = 50003; // timeout
381    
382    public function request($method, $url, $headers = [], $params = []) {
383
384        if ($this->failAttempts > 0) {
385            preg_match('/\/\/([a-z0-9\.\-]+)\//', $url, $m);
386            $this->failedHosts[] = $m[1];
387            
388            $this->failAttempts--;
389
390            throw new AblyRequestException( 'Fake error', $this->errorCode, $this->httpErrorCode );
391        }
392
393        return [
394            'headers' => 'HTTP/1.1 200 OK'."\n",
395            'body' => [ 999999, 0 ],
396        ];
397    }
398}