3 * @copyright Copyright (C) 2010-2022, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 namespace Friendica\Test\legacy;
26 use Friendica\Core\ACL;
27 use Friendica\Core\Config\Capability\IManageConfigValues;
29 use Friendica\Module\BaseApi;
30 use Friendica\Security\BasicAuth;
31 use Friendica\Test\FixtureTest;
32 use Friendica\Util\Arrays;
33 use Friendica\Util\DateTimeFormat;
34 use Monolog\Handler\TestHandler;
36 require_once __DIR__ . '/../../include/api.php';
39 * Tests for the API functions.
41 * Functions that use header() need to be tested in a separate process.
42 * @see https://phpunit.de/manual/5.7/en/appendixes.annotations.html#appendixes.annotations.runTestsInSeparateProcesses
44 * @backupGlobals enabled
46 class ApiTest extends FixtureTest
49 * @var TestHandler Can handle log-outputs
56 protected $friendUser;
60 protected $wrongUserId;
65 /** @var IManageConfigValues */
69 * Create variables used by tests.
71 protected function setUp() : void
73 global $API, $called_api;
79 /** @var IManageConfigValues $config */
80 $this->config = $this->dice->create(IManageConfigValues::class);
82 $this->config->set('system', 'url', 'http://localhost');
83 $this->config->set('system', 'hostname', 'localhost');
84 $this->config->set('system', 'worker_dont_fork', true);
87 $this->config->set('config', 'hostname', 'localhost');
88 $this->config->set('system', 'throttle_limit_day', 100);
89 $this->config->set('system', 'throttle_limit_week', 100);
90 $this->config->set('system', 'throttle_limit_month', 100);
91 $this->config->set('system', 'theme', 'system_theme');
95 $this->app = DI::app();
97 DI::args()->setArgc(1);
99 // User data that the test database is populated with
102 'name' => 'Self contact',
103 'nick' => 'selfcontact',
104 'nurl' => 'http://localhost/profile/selfcontact'
106 $this->friendUser = [
108 'name' => 'Friend contact',
109 'nick' => 'friendcontact',
110 'nurl' => 'http://localhost/profile/friendcontact'
114 'name' => 'othercontact',
115 'nick' => 'othercontact',
116 'nurl' => 'http://localhost/profile/othercontact'
119 // User ID that we know is not in the database
120 $this->wrongUserId = 666;
122 DI::session()->start();
124 // Most API require login so we force the session
126 'authenticated' => true,
127 'uid' => $this->selfUser['id']
129 BasicAuth::setCurrentUserID($this->selfUser['id']);
133 * Assert that a list array contains expected keys.
135 * @param array $list List array
139 private function assertList(array $list = [])
141 self::assertIsString($list['name']);
142 self::assertIsInt($list['id']);
143 self::assertIsString('string', $list['id_str']);
144 self::assertContains($list['mode'], ['public', 'private']);
145 // We could probably do more checks here.
149 * Assert that the string is XML and contain the root element.
151 * @param string $result XML string
152 * @param string $root_element Root element name
156 private function assertXml($result = '', $root_element = '')
158 self::assertStringStartsWith('<?xml version="1.0"?>', $result);
159 self::assertStringContainsString('<' . $root_element, $result);
160 // We could probably do more checks here.
164 * Test the api_user() function.
168 public function testApiUser()
170 self::assertEquals($this->selfUser['id'], BaseApi::getCurrentUserID());
176 * Test the api_source() function.
180 public function testApiSource()
182 self::assertEquals('api', BasicAuth::getCurrentApplicationToken()['name']);
186 * Test the api_source() function with a Twidere user agent.
190 public function testApiSourceWithTwidere()
192 $_SERVER['HTTP_USER_AGENT'] = 'Twidere';
193 self::assertEquals('Twidere', BasicAuth::getCurrentApplicationToken()['name']);
197 * Test the api_source() function with a GET parameter.
201 public function testApiSourceWithGet()
203 $_REQUEST['source'] = 'source_name';
204 self::assertEquals('source_name', BasicAuth::getCurrentApplicationToken()['name']);
208 * Test the api_date() function.
212 public function testApiDate()
214 self::assertEquals('Wed Oct 10 00:00:00 +0000 1990', DateTimeFormat::utc('1990-10-10', DateTimeFormat::API));
218 * Test the api_register_func() function.
222 public function testApiRegisterFunc()
234 self::assertTrue(is_callable($API['api_path']['func']));
238 * Test the BasicAuth::getCurrentUserID() function without any login.
240 * @runInSeparateProcess
241 * @preserveGlobalState disabled
242 * @preserveGlobalState disabled
244 public function testApiLoginWithoutLogin()
246 BasicAuth::setCurrentUserID();
247 $this->expectException(\Friendica\Network\HTTPException\UnauthorizedException::class);
248 BasicAuth::getCurrentUserID(true);
252 * Test the BasicAuth::getCurrentUserID() function with a bad login.
254 * @runInSeparateProcess
255 * @preserveGlobalState disabled
256 * @preserveGlobalState disabled
258 public function testApiLoginWithBadLogin()
260 BasicAuth::setCurrentUserID();
261 $this->expectException(\Friendica\Network\HTTPException\UnauthorizedException::class);
262 $_SERVER['PHP_AUTH_USER'] = 'user@server';
263 BasicAuth::getCurrentUserID(true);
267 * Test the BasicAuth::getCurrentUserID() function with oAuth.
271 public function testApiLoginWithOauth()
273 $this->markTestIncomplete('Can we test this easily?');
277 * Test the BasicAuth::getCurrentUserID() function with authentication provided by an addon.
281 public function testApiLoginWithAddonAuth()
283 $this->markTestIncomplete('Can we test this easily?');
287 * Test the BasicAuth::getCurrentUserID() function with a correct login.
289 * @runInSeparateProcess
290 * @preserveGlobalState disabled
291 * @doesNotPerformAssertions
293 public function testApiLoginWithCorrectLogin()
295 BasicAuth::setCurrentUserID();
296 $_SERVER['PHP_AUTH_USER'] = 'Test user';
297 $_SERVER['PHP_AUTH_PW'] = 'password';
298 BasicAuth::getCurrentUserID(true);
302 * Test the BasicAuth::getCurrentUserID() function with a remote user.
304 * @runInSeparateProcess
305 * @preserveGlobalState disabled
307 public function testApiLoginWithRemoteUser()
309 BasicAuth::setCurrentUserID();
310 $this->expectException(\Friendica\Network\HTTPException\UnauthorizedException::class);
311 $_SERVER['REDIRECT_REMOTE_USER'] = '123456dXNlcjpwYXNzd29yZA==';
312 BasicAuth::getCurrentUserID(true);
316 * Test the api_call() function.
318 * @runInSeparateProcess
319 * @preserveGlobalState disabled
321 public function testApiCall()
325 'method' => 'method',
326 'func' => function () {
327 return ['data' => ['some_data']];
330 $_SERVER['REQUEST_METHOD'] = 'method';
331 $_SERVER['QUERY_STRING'] = 'pagename=api_path';
332 $_GET['callback'] = 'callback_name';
335 'callback_name(["some_data"])',
336 api_call('api_path', 'json')
341 * Test the api_call() function with the profiled enabled.
343 * @runInSeparateProcess
344 * @preserveGlobalState disabled
346 public function testApiCallWithProfiler()
350 'method' => 'method',
351 'func' => function () {
352 return ['data' => ['some_data']];
356 $_SERVER['REQUEST_METHOD'] = 'method';
357 $_SERVER['QUERY_STRING'] = 'pagename=api_path';
359 $this->config->set('system', 'profiler', true);
360 $this->config->set('rendertime', 'callstack', true);
361 $this->app->callstack = [
362 'database' => ['some_function' => 200],
363 'database_write' => ['some_function' => 200],
364 'cache' => ['some_function' => 200],
365 'cache_write' => ['some_function' => 200],
366 'network' => ['some_function' => 200]
371 api_call('api_path', 'json')
376 * Test the api_call() function with a JSON result.
378 * @runInSeparateProcess
379 * @preserveGlobalState disabled
381 public function testApiCallWithJson()
385 'method' => 'method',
386 'func' => function () {
387 return ['data' => ['some_data']];
390 $_SERVER['REQUEST_METHOD'] = 'method';
391 $_SERVER['QUERY_STRING'] = 'pagename=api_path.json';
395 api_call('api_path.json', 'json')
400 * Test the api_call() function with an XML result.
402 * @runInSeparateProcess
403 * @preserveGlobalState disabled
405 public function testApiCallWithXml()
409 'method' => 'method',
410 'func' => function () {
414 $_SERVER['REQUEST_METHOD'] = 'method';
415 $_SERVER['QUERY_STRING'] = 'pagename=api_path.xml';
417 $args = DI::args()->determine($_SERVER, $_GET);
421 api_call('api_path.xml', 'xml')
426 * Test the api_call() function with an RSS result.
428 * @runInSeparateProcess
429 * @preserveGlobalState disabled
431 public function testApiCallWithRss()
435 'method' => 'method',
436 'func' => function () {
440 $_SERVER['REQUEST_METHOD'] = 'method';
441 $_SERVER['QUERY_STRING'] = 'pagename=api_path.rss';
444 '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
446 api_call('api_path.rss', 'rss')
451 * Test the api_call() function with an Atom result.
453 * @runInSeparateProcess
454 * @preserveGlobalState disabled
456 public function testApiCallWithAtom()
460 'method' => 'method',
461 'func' => function () {
465 $_SERVER['REQUEST_METHOD'] = 'method';
466 $_SERVER['QUERY_STRING'] = 'pagename=api_path.atom';
469 '<?xml version="1.0" encoding="UTF-8"?>' . "\n" .
471 api_call('api_path.atom', 'atom')
476 * Test the Arrays::walkRecursive() function.
480 public function testApiWalkRecursive()
485 Arrays::walkRecursive(
488 // Should we test this with a callback that actually does something?
496 * Test the Arrays::walkRecursive() function with an array.
500 public function testApiWalkRecursiveWithArray()
502 $array = [['item1'], ['item2']];
505 Arrays::walkRecursive(
508 // Should we test this with a callback that actually does something?
516 * Test the api_lists_list() function.
520 public function testApiListsList()
522 $result = api_lists_list('json');
523 self::assertEquals(['lists_list' => []], $result);
527 * Test the api_lists_ownerships() function.
531 public function testApiListsOwnerships()
533 $result = api_lists_ownerships('json');
534 foreach ($result['lists']['lists'] as $list) {
535 self::assertList($list);
540 * Test the api_lists_ownerships() function without an authenticated user.
544 public function testApiListsOwnershipsWithoutAuthenticatedUser()
546 $this->expectException(\Friendica\Network\HTTPException\UnauthorizedException::class);
547 BasicAuth::setCurrentUserID();
548 $_SESSION['authenticated'] = false;
549 api_lists_ownerships('json');
553 * Test the api_fr_photos_list() function.
557 public function testApiFrPhotosList()
559 $result = api_fr_photos_list('json');
560 self::assertArrayHasKey('photo', $result);
564 * Test the api_fr_photos_list() function without an authenticated user.
568 public function testApiFrPhotosListWithoutAuthenticatedUser()
570 $this->expectException(\Friendica\Network\HTTPException\UnauthorizedException::class);
571 BasicAuth::setCurrentUserID();
572 $_SESSION['authenticated'] = false;
573 api_fr_photos_list('json');
577 * Test the api_fr_photo_create_update() function.
579 public function testApiFrPhotoCreateUpdate()
581 $this->expectException(\Friendica\Network\HTTPException\BadRequestException::class);
582 api_fr_photo_create_update('json');
586 * Test the api_fr_photo_create_update() function without an authenticated user.
590 public function testApiFrPhotoCreateUpdateWithoutAuthenticatedUser()
592 $this->expectException(\Friendica\Network\HTTPException\UnauthorizedException::class);
593 BasicAuth::setCurrentUserID();
594 $_SESSION['authenticated'] = false;
595 api_fr_photo_create_update('json');
599 * Test the api_fr_photo_create_update() function with an album name.
603 public function testApiFrPhotoCreateUpdateWithAlbum()
605 $this->expectException(\Friendica\Network\HTTPException\BadRequestException::class);
606 $_REQUEST['album'] = 'album_name';
607 api_fr_photo_create_update('json');
611 * Test the api_fr_photo_create_update() function with the update mode.
615 public function testApiFrPhotoCreateUpdateWithUpdate()
617 $this->markTestIncomplete('We need to create a dataset for this');
621 * Test the api_fr_photo_create_update() function with an uploaded file.
625 public function testApiFrPhotoCreateUpdateWithFile()
627 $this->markTestIncomplete();
631 * Test the api_fr_photo_detail() function.
635 public function testApiFrPhotoDetail()
637 $this->expectException(\Friendica\Network\HTTPException\BadRequestException::class);
638 api_fr_photo_detail('json');
642 * Test the api_fr_photo_detail() function without an authenticated user.
646 public function testApiFrPhotoDetailWithoutAuthenticatedUser()
648 $this->expectException(\Friendica\Network\HTTPException\UnauthorizedException::class);
649 BasicAuth::setCurrentUserID();
650 $_SESSION['authenticated'] = false;
651 api_fr_photo_detail('json');
655 * Test the api_fr_photo_detail() function with a photo ID.
659 public function testApiFrPhotoDetailWithPhotoId()
661 $this->expectException(\Friendica\Network\HTTPException\NotFoundException::class);
662 $_REQUEST['photo_id'] = 1;
663 api_fr_photo_detail('json');
667 * Test the api_fr_photo_detail() function with a correct photo ID.
671 public function testApiFrPhotoDetailCorrectPhotoId()
673 $this->markTestIncomplete('We need to create a dataset for this.');
677 * Test the api_account_update_profile_image() function.
681 public function testApiAccountUpdateProfileImage()
683 $this->expectException(\Friendica\Network\HTTPException\BadRequestException::class);
684 api_account_update_profile_image('json');
688 * Test the api_account_update_profile_image() function without an authenticated user.
692 public function testApiAccountUpdateProfileImageWithoutAuthenticatedUser()
694 $this->expectException(\Friendica\Network\HTTPException\UnauthorizedException::class);
695 BasicAuth::setCurrentUserID();
696 $_SESSION['authenticated'] = false;
697 api_account_update_profile_image('json');
701 * Test the api_account_update_profile_image() function with an uploaded file.
705 public function testApiAccountUpdateProfileImageWithUpload()
707 $this->expectException(\Friendica\Network\HTTPException\BadRequestException::class);
708 $this->markTestIncomplete();
712 * Test the save_media_to_database() function.
716 public function testSaveMediaToDatabase()
718 $this->markTestIncomplete();
722 * Test the post_photo_item() function.
726 public function testPostPhotoItem()
728 $this->markTestIncomplete();
732 * Test the prepare_photo_data() function.
736 public function testPreparePhotoData()
738 $this->markTestIncomplete();
742 * Test the api_friendica_group_show() function.
746 public function testApiFriendicaGroupShow()
748 $this->markTestIncomplete();
752 * Test the api_lists_destroy() function.
756 public function testApiListsDestroy()
758 $this->markTestIncomplete();
762 * Test the group_create() function.
766 public function testGroupCreate()
768 $this->markTestIncomplete();
772 * Test the api_friendica_group_create() function.
776 public function testApiFriendicaGroupCreate()
778 $this->markTestIncomplete();
782 * Test the api_lists_create() function.
786 public function testApiListsCreate()
788 $this->markTestIncomplete();
792 * Test the api_lists_update() function.
796 public function testApiListsUpdate()
798 $this->markTestIncomplete();