diff --git a/.travis.yml b/.travis.yml index c3bcccd..e178bc2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,9 @@ script: - ./vendor/bin/phpunit -c tests/phpunit.unit.sqlite.xml - ./vendor/bin/phpunit -c tests/phpunit.unit.postgres.xml - ./vendor/bin/phpunit -c tests/phpunit.functional.sqlite.xml - - cp ./tests/ci/config.postgres.php $TRAVIS_BUILD_DIR/config.php && ./vendor/bin/phpunit -c tests/phpunit.functional.postgres.xml + - cp ./tests/ci/config.postgres.php $TRAVIS_BUILD_DIR/config.php + - ./vendor/bin/phpunit -c tests/phpunit.functional.postgres.xml tests/functional/ApiTest.php + - ./vendor/bin/phpunit -c tests/phpunit.functional.postgres.xml tests/functional/FeverApiTest.php after_failure: - cat apache_error.log diff --git a/ChangeLog b/ChangeLog index 25c1a55..9852741 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,7 +16,7 @@ Version 1.2.0 (unreleased) * Add support for Expires and Cache-Control headers (HTTP cache) * Update Docker image to Ubuntu 16.04 and PHP 7.0 * Add Docker compose file -* Add functional tests (Json-RPC API) +* Add functional tests (Json-RPC API and Fever API) * Add unit tests Migration procedure from 1.1.x to 1.2.0: diff --git a/app/models/bookmark.php b/app/models/bookmark.php index 3f08768..21f1db1 100644 --- a/app/models/bookmark.php +++ b/app/models/bookmark.php @@ -56,6 +56,7 @@ function get_bookmarked_item_ids($user_id) ->table(Model\Item\TABLE) ->eq('user_id', $user_id) ->eq('bookmark', 1) + ->asc('id') ->findAllByColumn('id'); } diff --git a/app/models/item.php b/app/models/item.php index 94ba28b..936e35a 100644 --- a/app/models/item.php +++ b/app/models/item.php @@ -294,6 +294,7 @@ function get_item_ids_by_status($user_id, $status) ->table('items') ->eq('user_id', $user_id) ->eq('status', $status) + ->asc('id') ->findAllByColumn('id'); } diff --git a/fever/index.php b/fever/index.php index f65f39b..b1fe0c8 100644 --- a/fever/index.php +++ b/fever/index.php @@ -50,11 +50,20 @@ route('groups', function () { list($user, $authenticated, $response) = auth(); if ($authenticated) { - $response['groups'] = Model\Group\get_all($user['id']); + $response['groups'] = array(); $response['feeds_groups'] = array(); - $group_map = Model\Group\get_groups_feed_ids($user['id']); - foreach ($group_map as $group_id => $feed_ids) { + $groups = Model\Group\get_all($user['id']); + $feed_groups = Model\Group\get_groups_feed_ids($user['id']); + + foreach ($groups as $group) { + $response['groups'][] = array( + 'id' => $group['id'], + 'title' => $group['title'], + ); + } + + foreach ($feed_groups as $group_id => $feed_ids) { $response['feeds_groups'][] = array( 'group_id' => $group_id, 'feed_ids' => implode(',', $feed_ids) @@ -102,7 +111,6 @@ route('feeds', function () { // Call: ?api&favicons route('favicons', function () { list($user, $authenticated, $response) = auth(); - if ($authenticated) { $favicons = Model\Favicon\get_favicons_with_data_url($user['id']); $response['favicons'] = array(); @@ -110,7 +118,7 @@ route('favicons', function () { foreach ($favicons as $favicon) { $response['favicons'][] = array( 'id' => (int) $favicon['feed_id'], - 'data' => $favicon['url'], + 'data' => $favicon['data_url'], ); } } diff --git a/tests/ci/config.postgres.php b/tests/ci/config.postgres.php index 1242b45..a0fd293 100644 --- a/tests/ci/config.postgres.php +++ b/tests/ci/config.postgres.php @@ -1,7 +1,7 @@ assertTrue($this->getApiClient()->removeFeed(1)); } - - protected function getApiClient(array $user = array()) - { - if (empty($user)) { - $user = $this->adminUser; - } - - $apiUserClient = new Client(API_URL); - $apiUserClient->authentication($user['username'], $user['api_token']); - - return $apiUserClient; - } } diff --git a/tests/functional/BaseApiTest.php b/tests/functional/BaseApiTest.php index c4f31bc..5bc81de 100644 --- a/tests/functional/BaseApiTest.php +++ b/tests/functional/BaseApiTest.php @@ -1,19 +1,41 @@ exec("SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE pg_stat_activity.datname = '".DB_NAME."' AND pid <> pg_backend_pid()"); + $pdo->exec('DROP DATABASE '.DB_NAME); $pdo->exec('CREATE DATABASE '.DB_NAME.' WITH OWNER '.DB_USERNAME); + $pdo = null; + } else if (file_exists(DB_FILENAME)) { + unlink(DB_FILENAME); } + } + public function setUp() + { $db = Miniflux\Database\get_connection(); $this->adminUser = $db->table(Miniflux\Model\User\TABLE)->eq('username', 'admin')->findOne(); } + + protected function getApiClient(array $user = array()) + { + if (empty($user)) { + $user = $this->adminUser; + } + + $apiUserClient = new Client(API_URL); + $apiUserClient->authentication($user['username'], $user['api_token']); + + return $apiUserClient; + } } diff --git a/tests/functional/FeverApiTest.php b/tests/functional/FeverApiTest.php new file mode 100644 index 0000000..3cf4769 --- /dev/null +++ b/tests/functional/FeverApiTest.php @@ -0,0 +1,223 @@ +executeFeverApiCall(); + $this->assertEquals(3, $response['api_version']); + $this->assertEquals(1, $response['auth']); + $this->assertArrayHasKey('last_refreshed_on_time', $response); + } + + public function testGetLinks() + { + $response = $this->executeFeverApiCall('links'); + $this->assertEquals(3, $response['api_version']); + $this->assertEquals(1, $response['auth']); + $this->assertArrayHasKey('last_refreshed_on_time', $response); + $this->assertSame(array(), $response['links']); + } + + public function testGetEmptyFeeds() + { + $response = $this->executeFeverApiCall('feeds'); + $this->assertEquals(3, $response['api_version']); + $this->assertEquals(1, $response['auth']); + $this->assertArrayHasKey('last_refreshed_on_time', $response); + $this->assertSame(array(), $response['feeds']); + $this->assertSame(array(), $response['feeds_groups']); + } + + public function testGetEmptyGroups() + { + $response = $this->executeFeverApiCall('groups'); + $this->assertEquals(3, $response['api_version']); + $this->assertEquals(1, $response['auth']); + $this->assertArrayHasKey('last_refreshed_on_time', $response); + $this->assertSame(array(), $response['groups']); + $this->assertSame(array(), $response['feeds_groups']); + } + + public function testGetFeedsAndGroups() + { + $this->createFeedAndGroups(); + + $response = $this->executeFeverApiCall('feeds'); + + $this->assertEquals(1, $response['feeds'][0]['id']); + $this->assertEquals(1, $response['feeds'][0]['favicon_id']); + $this->assertNotEmpty($response['feeds'][0]['title']); + $this->assertNotEmpty($response['feeds'][0]['url']); + $this->assertNotEmpty($response['feeds'][0]['site_url']); + $this->assertNotEmpty($response['feeds'][0]['last_updated_on_time']); + $this->assertEquals(0, $response['feeds'][0]['is_spark']); + + $this->assertEquals(array(array('group_id' => 1, 'feed_ids' => '1')), $response['feeds_groups']); + + $response = $this->executeFeverApiCall('groups'); + + $this->assertEquals(array(array('id' => 1, 'title' => 'open source software')), $response['groups']); + } + + public function testGetFavicons() + { + $response = $this->executeFeverApiCall('favicons'); + + $this->assertEquals(1, $response['favicons'][0]['id']); + $this->assertNotEmpty($response['favicons'][0]['data']); + } + + public function testGetItems() + { + $response = $this->executeFeverApiCall('items'); + + $this->assertGreaterThan(2, $response['total_items']); + $this->assertEquals(1, $response['items'][0]['id']); + $this->assertEquals(1, $response['items'][0]['feed_id']); + $this->assertNotEmpty($response['items'][0]['title']); + $this->assertNotEmpty($response['items'][0]['author']); + $this->assertNotEmpty($response['items'][0]['html']); + $this->assertNotEmpty($response['items'][0]['url']); + $this->assertEquals(0, $response['items'][0]['is_saved']); + $this->assertEquals(0, $response['items'][0]['is_read']); + $this->assertGreaterThan(0, $response['items'][0]['created_on_time']); + } + + public function testGetItemsWithIds() + { + $response = $this->executeFeverApiCall('items&with_ids=2,3'); + + $this->assertGreaterThan(2, $response['total_items']); + $this->assertCount(2, $response['items']); + $this->assertEquals(2, $response['items'][0]['id']); + $this->assertEquals(3, $response['items'][1]['id']); + } + + public function testGetItemsWithSinceId() + { + $response = $this->executeFeverApiCall('items&since_id=1'); + + $this->assertGreaterThan(2, $response['total_items']); + $this->assertEquals(2, $response['items'][0]['id']); + } + + public function testGetUnreadItems() + { + $response = $this->executeFeverApiCall('unread_item_ids'); + $this->assertStringStartsWith('1,2,', $response['unread_item_ids']); + } + + public function testMarkItemAsRead() + { + $this->assertNotNull($this->executeFeverApiCall('', array( + 'mark' => 'item', + 'as' => 'read', + 'id' => 1, + ))); + + $response = $this->executeFeverApiCall('items&with_ids=1'); + $this->assertEquals(1, $response['items'][0]['id']); + $this->assertEquals(1, $response['items'][0]['is_read']); + } + + public function testMarkItemAsSaved() + { + $this->assertNotNull($this->executeFeverApiCall('', array( + 'mark' => 'item', + 'as' => 'saved', + 'id' => 2, + ))); + + $response = $this->executeFeverApiCall('items&with_ids=2'); + $this->assertEquals(2, $response['items'][0]['id']); + $this->assertEquals(0, $response['items'][0]['is_read']); + $this->assertEquals(1, $response['items'][0]['is_saved']); + + $response = $this->executeFeverApiCall('saved_item_ids'); + $this->assertStringStartsWith('2', $response['saved_item_ids']); + } + + public function testMarkItemAsUnSaved() + { + $this->assertNotNull($this->executeFeverApiCall('', array( + 'mark' => 'item', + 'as' => 'unsaved', + 'id' => 2, + ))); + + $response = $this->executeFeverApiCall('items&with_ids=2'); + $this->assertEquals(2, $response['items'][0]['id']); + $this->assertEquals(0, $response['items'][0]['is_read']); + $this->assertEquals(0, $response['items'][0]['is_saved']); + } + + public function testMarkFeedAsRead() + { + $response = $this->executeFeverApiCall('items'); + $items = $response['items']; + $nbItems = count($items); + + $this->assertNotNull($this->executeFeverApiCall('', array( + 'mark' => 'feed', + 'as' => 'read', + 'id' => 1, + 'before' => $items[$nbItems - 1]['created_on_time'], + ))); + + $response = $this->executeFeverApiCall('items&with_ids=' . $items[$nbItems - 2]['id']); + $this->assertEquals(0, $response['items'][0]['is_read']); + + $response = $this->executeFeverApiCall('items&with_ids=' . $items[$nbItems - 1]['id']); + $this->assertEquals(1, $response['items'][0]['is_read']); + } + + public function testMarkGroupAsRead() + { + $this->assertNotNull($this->executeFeverApiCall('', array( + 'mark' => 'group', + 'as' => 'read', + 'id' => 1, + 'before' => time(), + ))); + + $response = $this->executeFeverApiCall('unread_item_ids'); + $this->assertSame('', $response['unread_item_ids']); + } + + protected function executeFeverApiCall($endpoint = '', array $data = array()) + { + $url = FEVER_API_URL . '?api&' . $endpoint; + $headers = array( + 'Content-type: application/x-www-form-urlencoded', + 'Accept: application/json', + ); + + $payload = array( + 'api_key' => $this->adminUser['fever_api_key'], + ); + + $context = stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'protocol_version' => 1.1, + 'timeout' => 2, + 'header' => implode("\r\n", $headers), + 'content' => http_build_query(array_merge($payload, $data)), + ), + )); + + $stream = fopen($url, 'r', false, $context); + return json_decode(stream_get_contents($stream), true); + } + + protected function createFeedAndGroups() + { + $this->assertNotFalse($this->getApiClient()->createFeed(array( + 'url' => 'https://miniflux.net/feed', + 'group_name' => 'open source software', + ))); + } +} diff --git a/tests/phpunit.functional.postgres.xml b/tests/phpunit.functional.postgres.xml index 873d284..add6bae 100644 --- a/tests/phpunit.functional.postgres.xml +++ b/tests/phpunit.functional.postgres.xml @@ -6,5 +6,10 @@ + + + + + diff --git a/tests/phpunit.functional.sqlite.xml b/tests/phpunit.functional.sqlite.xml index 061657a..747ad9b 100644 --- a/tests/phpunit.functional.sqlite.xml +++ b/tests/phpunit.functional.sqlite.xml @@ -6,6 +6,7 @@ +