1<?php
2
3namespace MatrixPhp\Tests;
4
5use MatrixPhp\Exceptions\MatrixException;
6use MatrixPhp\Exceptions\MatrixHttpLibException;
7use MatrixPhp\Exceptions\ValidationException;
8use GuzzleHttp\Client;
9use GuzzleHttp\Psr7\Response;
10use GuzzleHttp\Psr7\Request;
11use phpDocumentor\Reflection\DocBlock\Tags\Param;
12
13class MatrixHttpApiTest extends BaseTestCase {
14    protected $userId = "@alice:matrix.org";
15    protected $roomId = '#foo:matrix.org';
16    protected $token = "Dp0YKRXwx0iWDhFj7lg3DVjwsWzGcUIgARljgyAip2JD8qd5dSaWcxowTKEFetPulfLijAhv8eO"
17    . "mUSScyGcWgZyNMRTBmoJ0RFc0HotPvTBZU98yKRLtat7V43aCpFmK";
18    protected $testPath = "/account/whoami";
19    protected $deviceId = "QBUAZIFURK";
20    protected $displayName = "test_name";
21    protected $authBody = [
22        "auth" => [
23            "type" => "example.type.foo",
24            "session" => "xxxxx",
25            "example_credential" => "verypoorsharedsecret"
26        ]
27    ];
28    protected $oneTimeKeys = ["curve25519:AAAAAQ" => "/qyvZvwjiTxGdGU0RCguDCLeR+nmsb3FfNG3/Ve4vU8"];
29    protected $deviceKeys = [
30        "user_id" => "@alice:example.com",
31        "device_id" => "JLAFKJWSCS",
32        "algorithms" => [
33            "m.olm.curve25519-aes-sha256",
34            "m.megolm.v1.aes-sha"
35        ],
36        "keys" => [
37            "curve25519:JLAFKJWSCS" => "3C5BFWi2Y8MaVvjM8M22DBmh24PmgR0nPvJOIArzgyI",
38            "ed25519:JLAFKJWSCS" => "lEuiRJBit0IG6nUf5pUzWTUEsRVVe/HJkoKuEww9ULI"
39        ],
40        "signatures" => [
41            "@alice:example.com" => [
42                "ed25519:JLAFKJWSCS" => ("dSO80A01XiigH3uBiDVx/EjzaoycHcjq9lfQX0uWsqxl2gi"
43                    . "MIiSPR8a4d291W1ihKJL/a+myXS367WT6NAIcBA")
44            ]
45        ]
46    ];
47
48    protected $mxurl = "mxc://example.com/OonjUOmcuVpUnmOWKtzPmAFe";
49    /**
50     * @var MatrixHttpApi
51     */
52    protected $api;
53
54    protected function setUp(): void
55    {
56        parent::setUp();
57        $this->api = new MatrixHttpApi('http://example.com');
58    }
59
60    ///////////////////////////
61    // class TestTagsApi
62    ///////////////////////////
63    public function testGetUserTags() {
64        $tagsUrl = "http://example.com/_matrix/client/r0/user/%40alice%3Amatrix.org/rooms/%23foo%3Amatrix.org/tags";
65        $container = [];
66        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
67        $this->api->setClient(new Client(['handler' => $handler]));
68        $this->api->getUserTags($this->userId, $this->roomId);
69        /** @var Request $req */
70        $req = array_get($container, '0.request');
71
72        $this->assertEquals('GET', $req->getMethod());
73        $this->assertEquals($tagsUrl, (string)$req->getUri());
74    }
75
76    public function testAddUserTag() {
77        $tagsUrl = "http://example.com/_matrix/client/r0/user/%40alice%3Amatrix.org/rooms/%23foo%3Amatrix.org/tags/foo";
78        $container = [];
79        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
80        $this->api->setClient(new Client(['handler' => $handler]));
81        $this->api->addUserTag($this->userId, $this->roomId, 'foo', null, ["order" => "5"]);
82
83        /** @var Request $req */
84        $req = array_get($container, '0.request');
85
86        $this->assertEquals('PUT', $req->getMethod());
87        $this->assertEquals($tagsUrl, (string)$req->getUri());
88    }
89
90    public function testRemoveUserTag() {
91        $tagsUrl = "http://example.com/_matrix/client/r0/user/%40alice%3Amatrix.org/rooms/%23foo%3Amatrix.org/tags/foo";
92        $container = [];
93        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
94        $this->api->setClient(new Client(['handler' => $handler]));
95        $this->api->removeUserTag($this->userId, $this->roomId, 'foo');
96
97        /** @var Request $req */
98        $req = array_get($container, '0.request');
99
100        $this->assertEquals('DELETE', $req->getMethod());
101        $this->assertEquals($tagsUrl, (string)$req->getUri());
102    }
103
104    ///////////////////////////
105    // class TestAccountDataApi
106    ///////////////////////////
107    public function testSetAccountData() {
108        $accountDataUrl = "http://example.com/_matrix/client/r0/user/%40alice%3Amatrix.org/account_data/foo";
109        $container = [];
110        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
111        $this->api->setClient(new Client(['handler' => $handler]));
112        $this->api->setAccountData($this->userId, 'foo', ['bar' => 1]);
113
114        /** @var Request $req */
115        $req = array_get($container, '0.request');
116
117        $this->assertEquals('PUT', $req->getMethod());
118        $this->assertEquals($accountDataUrl, (string)$req->getUri());
119    }
120
121    public function testSetRoomAccountData() {
122        $accountDataUrl = 'http://example.com/_matrix/client/r0/user/%40alice%3Amatrix.org/';
123        $accountDataUrl .= 'rooms/%23foo%3Amatrix.org/account_data/foo';
124        $container = [];
125        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
126        $this->api->setClient(new Client(['handler' => $handler]));
127        $this->api->setRoomAccountData($this->userId, $this->roomId, 'foo', ['bar' => 1]);
128
129        /** @var Request $req */
130        $req = array_get($container, '0.request');
131
132        $this->assertEquals('PUT', $req->getMethod());
133        $this->assertEquals($accountDataUrl, (string)$req->getUri());
134    }
135
136    ///////////////////////////
137    // class TestAccountDataApi
138    ///////////////////////////
139    public function testUnban() {
140        $unbanUrl = 'http://example.com/_matrix/client/r0/rooms/%23foo%3Amatrix.org/unban';
141        $container = [];
142        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
143        $this->api->setClient(new Client(['handler' => $handler]));
144        $this->api->unbanUser($this->roomId, $this->userId);
145
146        /** @var Request $req */
147        $req = array_get($container, '0.request');
148
149        $this->assertEquals('POST', $req->getMethod());
150        $this->assertEquals($unbanUrl, (string)$req->getUri());
151    }
152
153    ///////////////////////////
154    // class TestDeviceApi
155    ///////////////////////////
156    public function testGetDevices() {
157        $getDevicesUrl = "http://example.com/_matrix/client/r0/devices";
158        $container = [];
159        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
160        $this->api->setClient(new Client(['handler' => $handler]));
161        $this->api->getDevices();
162
163        /** @var Request $req */
164        $req = array_get($container, '0.request');
165
166        $this->assertEquals('GET', $req->getMethod());
167        $this->assertEquals($getDevicesUrl, (string)$req->getUri());
168    }
169
170    public function testGetDevice() {
171        $getDevicesUrl = "http://example.com/_matrix/client/r0/devices/" . $this->deviceId;
172        $container = [];
173        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
174        $this->api->setClient(new Client(['handler' => $handler]));
175        $this->api->getDevice($this->deviceId);
176
177        /** @var Request $req */
178        $req = array_get($container, '0.request');
179
180        $this->assertEquals('GET', $req->getMethod());
181        $this->assertEquals($getDevicesUrl, (string)$req->getUri());
182    }
183
184    public function testUpdateDeviceInfo() {
185        $getDevicesUrl = "http://example.com/_matrix/client/r0/devices/" . $this->deviceId;
186        $container = [];
187        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
188        $this->api->setClient(new Client(['handler' => $handler]));
189        $this->api->updateDeviceInfo($this->deviceId, $this->displayName);
190
191        /** @var Request $req */
192        $req = array_get($container, '0.request');
193
194        $this->assertEquals('PUT', $req->getMethod());
195        $this->assertEquals($getDevicesUrl, (string)$req->getUri());
196    }
197
198    public function testDeleteDevice() {
199        $getDevicesUrl = "http://example.com/_matrix/client/r0/devices/" . $this->deviceId;
200        $container = [];
201        // Test for 401 status code of User-Interactive Auth API
202        $handler = $this->getMockClientHandler([new Response(401, [], '{}')], $container);
203        $this->api->setClient(new Client(['handler' => $handler]));
204        $this->expectException(MatrixHttpLibException::class);
205
206        try {
207            $this->api->deleteDevice($this->authBody, $this->deviceId);
208        } catch (MatrixHttpLibException $e) {
209            /** @var Request $req */
210            $req = array_get($container, '0.request');
211
212            $this->assertEquals('DELETE', $req->getMethod());
213            $this->assertEquals($getDevicesUrl, (string)$req->getUri());
214
215            throw $e;
216        }
217    }
218
219    public function testDeleteDevices() {
220        $getDevicesUrl = "http://example.com/_matrix/client/r0/delete_devices/";
221        $container = [];
222        // Test for 401 status code of User-Interactive Auth API
223        $handler = $this->getMockClientHandler([new Response(401, [], '{}')], $container);
224        $this->api->setClient(new Client(['handler' => $handler]));
225        $this->expectException(MatrixHttpLibException::class);
226
227        $this->api->deleteDevices($this->authBody, [$this->deviceId]);
228
229        /** @var Request $req */
230        $req = array_get($container, '0.request');
231
232        $this->assertEquals('POST', $req->getMethod());
233        $this->assertEquals($getDevicesUrl, (string)$req->getUri());
234    }
235
236    ///////////////////////////
237    // class TestKeysApi
238    ///////////////////////////
239    /**
240     * @param array $args
241     * @throws Exceptions\MatrixRequestException
242     * @throws MatrixException
243     * @throws MatrixHttpLibException
244     * @dataProvider uploadKeysProvider
245     */
246    public function testUploadKeys(array $args) {
247        $uploadKeysUrl = 'http://example.com/_matrix/client/r0/keys/upload';
248        $container = [];
249        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
250        $this->api->setClient(new Client(['handler' => $handler]));
251        $this->api->uploadKeys($args);
252
253        /** @var Request $req */
254        $req = array_get($container, '0.request');
255
256        $this->assertEquals('POST', $req->getMethod());
257        $this->assertEquals($uploadKeysUrl, (string)$req->getUri());
258    }
259
260    public function uploadKeysProvider(): array {
261        return [
262            [[]],
263            [['device_keys' => $this->deviceKeys]],
264            [['one_time_keys' => $this->oneTimeKeys]],
265        ];
266    }
267
268    public function testQueryKeys() {
269        $queryKeyUrl = 'http://example.com/_matrix/client/r0/keys/query';
270        $container = [];
271        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
272        $this->api->setClient(new Client(['handler' => $handler]));
273        $this->api->queryKeys([$this->userId => $this->deviceId], 10);
274
275        /** @var Request $req */
276        $req = array_get($container, '0.request');
277
278        $this->assertEquals('POST', $req->getMethod());
279        $this->assertEquals($queryKeyUrl, (string)$req->getUri());
280    }
281
282    public function testClaimKeys() {
283        $claimKeysUrl = 'http://example.com/_matrix/client/r0/keys/claim';
284        $container = [];
285        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
286        $this->api->setClient(new Client(['handler' => $handler]));
287        $keyRequest = [$this->userId => [$this->deviceId => 'algo']];
288        $this->api->claimKeys($keyRequest, 1000);
289
290        /** @var Request $req */
291        $req = array_get($container, '0.request');
292
293        $this->assertEquals('POST', $req->getMethod());
294        $this->assertEquals($claimKeysUrl, (string)$req->getUri());
295    }
296
297    public function testKeyChange() {
298        $keyChangeUrl = 'http://example.com/_matrix/client/r0/keys/changes';
299        $container = [];
300        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
301        $this->api->setClient(new Client(['handler' => $handler]));
302        $this->api->keyChanges('s72594_4483_1934', 's75689_5632_2435');
303
304        /** @var Request $req */
305        $req = array_get($container, '0.request');
306
307        $this->assertEquals('GET', $req->getMethod());
308        $this->assertEquals($keyChangeUrl, explode('?', (string)$req->getUri())[0]);
309    }
310
311    ///////////////////////////
312    // class TestSendToDeviceApi
313    ///////////////////////////
314    public function testSendToDevice() {
315        $txnId = $this->invokePrivateMethod($this->api, 'makeTxnId');
316        $sendToDeviceUrl = "http://example.com/_matrix/client/r0/sendToDevice/m.new_device/" . $txnId;
317        $container = [];
318        $handler = $this->getMockClientHandler([new Response(200, [], '{}')], $container);
319        $this->api->setClient(new Client(['handler' => $handler]));
320        $payload = [$this->userId => [$this->deviceId => ['test' => 1]]];
321        $this->api->sendToDevice('m.new_device', $payload, $txnId);
322
323        /** @var Request $req */
324        $req = array_get($container, '0.request');
325
326        $this->assertEquals('PUT', $req->getMethod());
327        $this->assertEquals($sendToDeviceUrl, (string)$req->getUri());
328    }
329
330    ///////////////////////////
331    // class TestMainApi
332    ///////////////////////////
333    public function testSendTokenHeader() {
334        $mapi = new MatrixHttpApi("http://example.com", $this->token);
335
336        $r = sprintf('{"application/json": {"user_id": "%s"}}', $this->userId);
337        $container = [];
338        $handler = $this->getMockClientHandler([new Response(200, [], $r)], $container);
339        $mapi->setClient(new Client(['handler' => $handler]));
340
341        $this->invokePrivateMethod($mapi, 'send', ['GET', $this->testPath]);
342        /** @var Request $req */
343        $req = array_get($container, '0.request');
344
345        $this->assertEquals('GET', $req->getMethod());
346        $this->assertEquals(sprintf('Bearer %s', $this->token), $req->getHeader('Authorization')[0]);
347    }
348
349    public function testSendUserAgentHeader() {
350        $mapi = new MatrixHttpApi("http://example.com", $this->token);
351
352        $r = sprintf('{"application/json": {"user_id": "%s"}}', $this->userId);
353        $container = [];
354        $handler = $this->getMockClientHandler([new Response(200, [], $r)], $container);
355        $mapi->setClient(new Client(['handler' => $handler]));
356
357        $this->invokePrivateMethod($mapi, 'send', ['GET', $this->testPath]);
358        /** @var Request $req */
359        $req = array_get($container, '0.request');
360
361        $this->assertEquals('GET', $req->getMethod());
362        $this->assertEquals('php-matrix-sdk/' . $mapi::VERSION, $req->getHeader('User-Agent')[0]);
363    }
364
365    public function testSendTokenQuery() {
366        $mapi = new MatrixHttpApi("http://example.com", $this->token, null, 500, false);
367
368        $r = sprintf('{"application/json": {"user_id": "%s"}}', $this->userId);
369        $container = [];
370        $handler = $this->getMockClientHandler([new Response(200, [], $r)], $container);
371        $mapi->setClient(new Client(['handler' => $handler]));
372
373        $this->invokePrivateMethod($mapi, 'send', ['GET', $this->testPath]);
374        /** @var Request $req */
375        $req = array_get($container, '0.request');
376
377        $this->assertEquals('GET', $req->getMethod());
378        $this->assertStringContainsString($this->token, $req->getRequestTarget());
379    }
380
381    public function testSendUserId() {
382        $mapi = new MatrixHttpApi("http://example.com", $this->token, $this->userId);
383
384        $r = sprintf('{"application/json": {"user_id": "%s"}}', $this->userId);
385        $container = [];
386        $handler = $this->getMockClientHandler([new Response(200, [], $r)], $container);
387        $mapi->setClient(new Client(['handler' => $handler]));
388
389        $this->invokePrivateMethod($mapi, 'send', ['GET', $this->testPath]);
390        /** @var Request $req */
391        $req = array_get($container, '0.request');
392
393        $this->assertEquals('GET', $req->getMethod());
394        $this->assertStringContainsString(urlencode($this->userId), $req->getRequestTarget());
395    }
396
397    public function testSendUnsupMethod() {
398        $this->expectException(MatrixException::class);
399        $mapi = new MatrixHttpApi("http://example.com", $this->token, $this->userId);
400        $this->invokePrivateMethod($mapi, 'send', ['GOT', $this->testPath]);
401    }
402
403    public function testSendRequestError() {
404        $this->expectException(MatrixHttpLibException::class);
405        $mapi = new MatrixHttpApi("http://example.com");
406        $this->invokePrivateMethod($mapi, 'send', ['GET', $this->testPath]);
407    }
408
409    ///////////////////////////
410    // class TestMediaApi
411    ///////////////////////////
412    public function testMediaDownload() {
413        $dlUrl = "http://example.com/_matrix/media/r0/download/" . substr($this->mxurl, 6);
414        $container = [];
415        $response = new Response(200, [
416            'Content-Type' => 'application/php'
417        ], file_get_contents('./tests/TestHelper.php'));
418        $handler = $this->getMockClientHandler([$response], $container);
419        $this->api->setClient(new Client(['handler' => $handler]));
420        $this->api->mediaDownload($this->mxurl, false);
421
422        /** @var Request $req */
423        $req = array_get($container, '0.request');
424
425        $this->assertEquals('GET', $req->getMethod());
426        $this->assertEquals($dlUrl, explode('?', (string)$req->getUri())[0]);
427    }
428
429    public function testMediaDownloadWrongUrl() {
430        $this->expectException(ValidationException::class);
431
432        $this->api->mediaDownload(substr($this->mxurl, 6));
433    }
434
435    public function testGetThumbnail() {
436        $dlUrl = "http://example.com/_matrix/media/r0/thumbnail/" . substr($this->mxurl, 6);
437        $container = [];
438        $response = new Response(200, [
439            'Content-Type' => 'application/php'
440        ], file_get_contents('./tests/TestHelper.php'));
441        $handler = $this->getMockClientHandler([$response], $container);
442        $this->api->setClient(new Client(['handler' => $handler]));
443        $this->api->getThumbnail($this->mxurl, 28, 28, 'scale', false);
444
445        /** @var Request $req */
446        $req = array_get($container, '0.request');
447
448        $this->assertEquals('GET', $req->getMethod());
449        $this->assertEquals($dlUrl, explode('?', (string)$req->getUri())[0]);
450    }
451
452    public function testThumbnailWrongUrl() {
453        $this->expectException(ValidationException::class);
454
455        $this->api->getThumbnail(substr($this->mxurl, 6), 28, 28);
456    }
457
458    public function testThumbnailWrongMethod() {
459        $this->expectException(ValidationException::class);
460
461        $this->api->getThumbnail($this->mxurl, 28, 28, 'cut', false);
462    }
463
464    public function testGetUrlPreview() {
465        $mediaUrl = "http://example.com/_matrix/media/r0/preview_url";
466        $r = json_encode(TestHelper::EXAMPLE_PREVIEW_URL);
467        $container = [];
468        $handler = $this->getMockClientHandler([new Response(200, [], $r)], $container);
469        $this->api->setClient(new Client(['handler' => $handler]));
470        $this->api->getUrlPreview("https://google.com/", 1510610716656);
471
472        /** @var Request $req */
473        $req = array_get($container, '0.request');
474
475        $this->assertEquals('GET', $req->getMethod());
476        $this->assertEquals($mediaUrl, explode('?', (string)$req->getUri())[0]);
477    }
478
479
480    ///////////////////////////
481    // class TestRoomApi
482    ///////////////////////////
483    /**
484     * @dataProvider createRoomVisibilityProvider
485     */
486    public function testCreateRoomVisibility(bool $isPublic, string $visibility) {
487        $createRoomUrl = "http://example.com/_matrix/client/r0/createRoom";
488        $container = [];
489        $r = '{"room_id": "!sefiuhWgwghwWgh:example.com"}';
490        $handler = $this->getMockClientHandler([new Response(200, [], $r)], $container);
491        $this->api->setClient(new Client(['handler' => $handler]));
492        $this->api->createRoom("#test:example.com", 'test', $isPublic);
493
494        /** @var Request $req */
495        $req = array_get($container, '0.request');
496
497        $this->assertEquals('POST', $req->getMethod());
498        $this->assertEquals($createRoomUrl, (string)$req->getUri());
499        $body = json_decode($req->getBody()->getContents(), true);
500        $this->assertEquals("#test:example.com", $body['room_alias_name']);
501        $this->assertEquals($visibility, $body['visibility']);
502        $this->assertEquals('test', $body['name']);
503    }
504
505    public function createRoomVisibilityProvider(): array {
506        return [
507            [true, 'public'],
508            [false, 'private'],
509        ];
510    }
511
512    /**
513     * @dataProvider createRoomFederationProvider
514     */
515    public function testCreateRoomFederation(bool $isFederated) {
516        $createRoomUrl = "http://example.com/_matrix/client/r0/createRoom";
517        $container = [];
518        $r = '{"room_id": "!sefiuhWgwghwWgh:example.com"}';
519        $handler = $this->getMockClientHandler([new Response(200, [], $r)], $container);
520        $this->api->setClient(new Client(['handler' => $handler]));
521
522        $this->api->createRoom("#test:example.com", 'test', false, [], $isFederated);
523        /** @var Request $req */
524        $req = array_get($container, '0.request');
525
526        $this->assertEquals('POST', $req->getMethod());
527        $this->assertEquals($createRoomUrl, (string)$req->getUri());
528        $body = json_decode($req->getBody()->getContents(), true);
529        $this->assertEquals("#test:example.com", $body['room_alias_name']);
530        $this->assertEquals($isFederated, array_key_exists('m.federate', array_get($body, 'creation_content', [])));
531    }
532
533
534    public function createRoomFederationProvider(): array {
535        return [
536            [true],
537            [false],
538        ];
539    }
540
541    ///////////////////////////
542    // class TestRoomApi
543    ///////////////////////////
544    public function testWhoami() {
545        $mapi = new MatrixHttpApi("http://example.com", $this->token);
546        $whoamiUrl = "http://example.com/_matrix/client/r0/account/whoami";
547
548        $r = sprintf('{"user_id": "%s"}', $this->userId);
549        $container = [];
550        $handler = $this->getMockClientHandler([new Response(200, [], $r)], $container);
551        $mapi->setClient(new Client(['handler' => $handler]));
552
553        $mapi->whoami();
554        /** @var Request $req */
555        $req = array_get($container, '0.request');
556
557        $this->assertEquals('GET', $req->getMethod());
558        $this->assertStringContainsString($req->getRequestTarget(), $whoamiUrl);
559    }
560
561    public function testWhoamiUnauth() {
562        $this->expectException(MatrixException::class);
563
564        $r = sprintf('{"user_id": "%s"}', $this->userId);
565        $container = [];
566        $handler = $this->getMockClientHandler([new Response(200, [], $r)], $container);
567        $this->api->setClient(new Client(['handler' => $handler]));
568
569        $this->api->whoami();
570    }
571
572
573}
574