Add functional API tests

This commit is contained in:
Frederic Guillot 2016-12-29 15:45:09 -05:00
parent f90bb969cc
commit 56d21dc726
14 changed files with 368 additions and 14 deletions

View File

@ -2,7 +2,7 @@ git:
depth: 3
language: php
sudo: false
sudo: required
php:
- 7.0
@ -10,11 +10,23 @@ php:
- 5.5
- 5.4
- 5.3
- hhvm
before_install:
- sudo apt-get update -qq
- sudo apt-get install -y apache2 libapache2-mod-fastcgi
install:
- composer install
before_script:
- composer install
- ./tests/ci/install.sh
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
after_failure:
- cat apache_error.log
- cat apache_access.log

View File

@ -16,6 +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 unit tests
Migration procedure from 1.1.x to 1.2.0:

View File

@ -41,6 +41,10 @@ $(JS_FILE): assets/js/app.js \
archive:
@ git archive --format=zip --prefix=miniflux/ v${version} -o ${dst}/miniflux-${version}.zip
functional-test-sqlite:
@ rm -f data/db.sqlite
@ ./vendor/bin/phpunit -c tests/phpunit.functional.sqlite.xml
unit-test-sqlite:
@ ./vendor/bin/phpunit -c tests/phpunit.unit.sqlite.xml

View File

@ -84,7 +84,7 @@ function create_feed($user_id, $url, $download_content = false, $rtl = false, $c
} else {
fetch_favicon($feed_id, $feed->getSiteUrl(), $feed->getIcon());
if (! empty($feed_group_ids)) {
if (! empty($feed_group_ids) || ! empty($group_name)) {
Model\Group\update_feed_groups($user_id, $feed_id, $feed_group_ids, $group_name);
}
}

View File

@ -141,7 +141,7 @@ function get_favicons_with_data_url($user_id)
->findAll();
foreach ($favicons as &$favicon) {
$favicon['url'] = get_favicon_data_url($favicon['hash'], $favicon['type']);
$favicon['data_url'] = get_favicon_data_url($favicon['hash'], $favicon['type']);
}
return $favicons;

View File

@ -2,12 +2,14 @@
require __DIR__.'/app/common.php';
use JsonRPC\Exception\AccessDeniedException;
use JsonRPC\Exception\AuthenticationFailureException;
use JsonRPC\MiddlewareInterface;
use JsonRPC\Server;
use Miniflux\Handler;
use Miniflux\Model;
use Miniflux\Session\SessionStorage;
use Miniflux\Validator;
class AuthMiddleware implements MiddlewareInterface
{
@ -28,7 +30,37 @@ $procedureHandler = $server->getProcedureHandler();
// Get version
$procedureHandler->withCallback('getVersion', function () {
return array('version' => APP_VERSION);
return APP_VERSION;
});
// Create user
$procedureHandler->withCallback('createUser', function ($username, $password, $is_admin = false) {
if (! SessionStorage::getInstance()->isAdmin()) {
throw new AccessDeniedException('Reserved to administrators');
}
$values = array(
'username' => $username,
'password' => $password,
'confirmation' => $password,
);
list($valid) = Validator\User\validate_creation($values);
if ($valid) {
return Model\User\create_user($username, $password, $is_admin);
}
return false;
});
// Get user
$procedureHandler->withCallback('getUserByUsername', function ($username) {
if (! SessionStorage::getInstance()->isAdmin()) {
throw new AccessDeniedException('Reserved to administrators');
}
return Model\User\get_user_by_username($username);
});
// Get all feeds
@ -46,13 +78,27 @@ $procedureHandler->withCallback('getFeeds', function () {
// Get one feed
$procedureHandler->withCallback('getFeed', function ($feed_id) {
$user_id = SessionStorage::getInstance()->getUserId();
return Model\Feed\get_feed($user_id, $feed_id);
$feed = Model\Feed\get_feed($user_id, $feed_id);
if (! empty($feed)) {
$feed['groups'] = Model\Group\get_feed_groups($feed['id']);
}
return $feed;
});
// Add a new feed
$procedureHandler->withCallback('createFeed', function ($url) {
$procedureHandler->withCallback('createFeed', function ($url, $download_content = false, $rtl = false, $group_name = null) {
$user_id = SessionStorage::getInstance()->getUserId();
list($feed_id,) = Handler\Feed\create_feed($user_id, $url);
list($feed_id,) = Handler\Feed\create_feed(
$user_id,
$url,
$download_content,
$rtl,
false,
array(),
$group_name
);
if ($feed_id > 0) {
return $feed_id;
@ -62,7 +108,7 @@ $procedureHandler->withCallback('createFeed', function ($url) {
});
// Delete a feed
$procedureHandler->withCallback('deleteFeed', function ($feed_id) {
$procedureHandler->withCallback('removeFeed', function ($feed_id) {
$user_id = SessionStorage::getInstance()->getUserId();
return Model\Feed\remove_feed($user_id, $feed_id);
});
@ -74,9 +120,9 @@ $procedureHandler->withCallback('refreshFeed', function ($feed_id) {
});
// Get all items
$procedureHandler->withCallback('getItems', function ($since_id = null, array $item_ids = array(), $offset = 50) {
$procedureHandler->withCallback('getItems', function ($since_id = null, array $item_ids = array(), $limit = 50) {
$user_id = SessionStorage::getInstance()->getUserId();
return Model\Item\get_items($user_id, $since_id, $item_ids, $offset);
return Model\Item\get_items($user_id, $since_id, $item_ids, $limit);
});
// Get one item

View File

@ -0,0 +1,19 @@
<VirtualHost *:80>
DocumentRoot %TRAVIS_BUILD_DIR%
ErrorLog "%TRAVIS_BUILD_DIR%/apache_error.log"
CustomLog "%TRAVIS_BUILD_DIR%/apache_access.log" combined
<Directory "%TRAVIS_BUILD_DIR%">
Options FollowSymLinks MultiViews ExecCGI
AllowOverride All
Order deny,allow
Allow from all
</Directory>
<IfModule mod_fastcgi.c>
AddHandler php5-fcgi .php
Action php5-fcgi /php5-fcgi
Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi
FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -host 127.0.0.1:9000 -pass-header Authorization
</IfModule>
</VirtualHost>

View File

@ -0,0 +1,7 @@
<?php
define('DB_DRIVER', 'postgres');
define('DB_HOSTNAME', 'localhost');
define('DB_NAME', 'miniflux_functional_test');
define('DB_USERNAME', 'postgres');
define('DB_PASSWORD', '');

18
tests/ci/install.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/sh
sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.conf
if [ "$TRAVIS_PHP_VERSION" = "7.0" -a -n "$(ls -A ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d)" ]; then
sudo cp ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf.default ~/.phpenv/versions/$(phpenv version-name)/etc/php-fpm.d/www.conf
fi
echo "cgi.fix_pathinfo = 1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
echo "always_populate_raw_post_data = -1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini
~/.phpenv/versions/$(phpenv version-name)/sbin/php-fpm
sudo a2enmod rewrite actions fastcgi alias ssl
sudo cp -f tests/ci/apache_vhost.conf /etc/apache2/sites-available/default
sudo sed -e "s?%TRAVIS_BUILD_DIR%?$(pwd)?g" --in-place /etc/apache2/sites-available/default
sudo service apache2 restart

View File

@ -0,0 +1,207 @@
<?php
use JsonRPC\Client;
require_once __DIR__.'/BaseApiTest.php';
class ApiTest extends BaseApiTest
{
public function testGetVersion()
{
$this->assertEquals('master', $this->getApiClient()->getVersion());
}
public function testCreateUser()
{
$this->assertFalse($this->getApiClient()->createUser('admin', 'test123'));
$this->assertNotFalse($this->getApiClient()->createUser(array(
'username' => 'api_test',
'password' => 'test123',
)));
}
public function testGetUser()
{
$this->assertNull($this->getApiClient()->getUserByUsername('notfound'));
$user = $this->getApiClient()->getUserByUsername('api_test');
$this->assertEquals('api_test', $user['username']);
$this->assertFalse((bool) $user['is_admin']);
$this->assertArrayHasKey('password', $user);
$this->assertArrayHasKey('api_token', $user);
}
public function testCreateUserAsNonAdmin()
{
$user = $this->getApiClient()->getUserByUsername('api_test');
$this->setExpectedException('JsonRPC\Exception\AccessDeniedException');
$this->getApiClient($user)->createUser('someone', 'secret');
}
public function testGetUserAsNonAdmin()
{
$user = $this->getApiClient()->getUserByUsername('api_test');
$this->setExpectedException('JsonRPC\Exception\AccessDeniedException');
$this->getApiClient($user)->getUserByUsername('admin');
}
public function testCreateFeed()
{
$this->assertNotFalse($this->getApiClient()->createFeed(array(
'url' => 'https://miniflux.net/feed',
'group_name' => 'open source software',
)));
}
public function testGetAllFeeds()
{
$feeds = $this->getApiClient()->getFeeds();
$this->assertCount(1, $feeds);
$this->assertEquals(1, $feeds[0]['id']);
$this->assertEquals('https://miniflux.net/feed', $feeds[0]['feed_url']);
$this->assertTrue((bool) $feeds[0]['enabled']);
$this->assertEquals('open source software', $feeds[0]['groups'][0]['title']);
}
public function testGetFeed()
{
$this->assertNull($this->getApiClient()->getFeed(999));
$feed = $this->getApiClient()->getFeed(1);
$this->assertEquals('https://miniflux.net/feed', $feed['feed_url']);
$this->assertTrue((bool) $feed['enabled']);
$this->assertEquals('open source software', $feed['groups'][0]['title']);
}
public function testRefreshFeed()
{
$this->assertTrue($this->getApiClient()->refreshFeed(1));
}
public function testGetItems()
{
$items = $this->getApiClient()->getItems();
$this->assertNotEmpty($items);
$this->assertEquals(1, $items[0]['id']);
$this->assertEquals(1, $items[0]['feed_id']);
$this->assertNotEmpty($items[0]['title']);
$this->assertNotEmpty($items[0]['author']);
$this->assertNotEmpty($items[0]['content']);
$this->assertNotEmpty($items[0]['url']);
}
public function testGetItemsSinceId()
{
$items = $this->getApiClient()->getItems(array('since_id' => 2));
$this->assertNotEmpty($items);
$this->assertEquals(3, $items[0]['id']);
}
public function testGetSpecificItems()
{
$items = $this->getApiClient()->getItems(array('item_ids' => array(2, 3)));
$this->assertNotEmpty($items);
$this->assertEquals(2, $items[0]['id']);
$this->assertEquals(3, $items[1]['id']);
}
public function testGetItem()
{
$this->assertNull($this->getApiClient()->getItem(999));
$item = $this->getApiClient()->getItem(1);
$this->assertNotEmpty($item);
$this->assertEquals(1, $item['id']);
$this->assertEquals(1, $item['feed_id']);
$this->assertEquals('unread', $item['status']);
$this->assertNotEmpty($item['title']);
$this->assertNotEmpty($item['author']);
$this->assertNotEmpty($item['content']);
$this->assertNotEmpty($item['url']);
}
public function testChangeItemsStatus()
{
$this->assertTrue($this->getApiClient()->changeItemsStatus(array(1), 'read'));
$item = $this->getApiClient()->getItem(1);
$this->assertEquals('read', $item['status']);
$item = $this->getApiClient()->getItem(2);
$this->assertEquals('unread', $item['status']);
}
public function testAddBookmark()
{
$this->assertTrue($this->getApiClient()->addBookmark(1));
$item = $this->getApiClient()->getItem(1);
$this->assertTrue((bool) $item['bookmark']);
}
public function testRemoveBookmark()
{
$this->assertTrue($this->getApiClient()->removeBookmark(1));
$item = $this->getApiClient()->getItem(1);
$this->assertFalse((bool) $item['bookmark']);
}
public function testGetGroups()
{
$groups = $this->getApiClient()->getGroups();
$this->assertCount(1, $groups);
$this->assertEquals(1, $groups[0]['id']);
$this->assertEquals(1, $groups[0]['user_id']);
$this->assertEquals('open source software', $groups[0]['title']);
}
public function testCreateGroup()
{
$this->assertEquals(2, $this->getApiClient()->createGroup('foobar'));
$this->assertEquals(2, $this->getApiClient()->createGroup('foobar'));
$groups = $this->getApiClient()->getGroups();
$this->assertCount(2, $groups);
}
public function testSetFeedGroups()
{
$this->assertTrue($this->getApiClient()->setFeedGroups(1, array(2)));
$feed = $this->getApiClient()->getFeed(1);
$this->assertCount(1, $feed['groups']);
$this->assertEquals('foobar', $feed['groups'][0]['title']);
}
public function testGetFavicons()
{
$favicons = $this->getApiClient()->getFavicons();
$this->assertCount(1, $favicons);
$this->assertEquals(1, $favicons[0]['feed_id']);
$this->assertNotEmpty($favicons[0]['hash']);
$this->assertNotEmpty($favicons[0]['type']);
$this->assertNotEmpty($favicons[0]['data_url']);
}
public function testDeleteFeed()
{
$this->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;
}
}

View File

@ -0,0 +1,19 @@
<?php
require_once __DIR__.'/../../app/common.php';
abstract class BaseApiTest extends PHPUnit_Framework_TestCase
{
protected $adminUser = array();
public function setUp()
{
if (DB_DRIVER === 'postgres') {
$pdo = new PDO('pgsql:host='.DB_HOSTNAME, DB_USERNAME, DB_PASSWORD);
$pdo->exec('CREATE DATABASE '.DB_NAME.' WITH OWNER '.DB_USERNAME);
}
$db = Miniflux\Database\get_connection();
$this->adminUser = $db->table(Miniflux\Model\User\TABLE)->eq('username', 'admin')->findOne();
}
}

View File

@ -0,0 +1,10 @@
<phpunit stopOnError="true" stopOnFailure="true" colors="true">
<testsuites>
<testsuite name="Miniflux Postgres Functional Tests">
<directory>functional</directory>
</testsuite>
</testsuites>
<php>
<const name="API_URL" value="http://127.0.0.1/jsonrpc.php" />
</php>
</phpunit>

View File

@ -0,0 +1,11 @@
<phpunit stopOnError="true" stopOnFailure="true" colors="true">
<testsuites>
<testsuite name="Miniflux Sqlite Functional Tests">
<directory>functional</directory>
</testsuite>
</testsuites>
<php>
<const name="API_URL" value="http://127.0.0.1/jsonrpc.php" />
<const name="DB_FILENAME" value="data/db.sqlite" />
</php>
</phpunit>

View File

@ -131,12 +131,12 @@ class FaviconModelTest extends BaseTest
$this->assertEquals(1, $favicons[0]['feed_id']);
$this->assertEquals('57978a20204f7af6967571041c79d907a8a8072c', $favicons[0]['hash']);
$this->assertEquals('image/png', $favicons[0]['type']);
$this->assertEquals('data:image/png;base64,YmluYXJ5IGRhdGE=', $favicons[0]['url']);
$this->assertEquals('data:image/png;base64,YmluYXJ5IGRhdGE=', $favicons[0]['data_url']);
$this->assertEquals(2, $favicons[1]['feed_id']);
$this->assertEquals('36242b50974c41478569d66616346ee5f2ad7b6e', $favicons[1]['hash']);
$this->assertEquals('image/gif', $favicons[1]['type']);
$this->assertEquals('data:image/gif;base64,c29tZSBiaW5hcnkgZGF0YQ==', $favicons[1]['url']);
$this->assertEquals('data:image/gif;base64,c29tZSBiaW5hcnkgZGF0YQ==', $favicons[1]['data_url']);
}
public function testGetItemsFavicons()