diff options
-rw-r--r-- | app/Model/Board.php | 15 | ||||
-rw-r--r-- | app/Model/Category.php | 15 | ||||
-rw-r--r-- | app/Model/Project.php | 15 | ||||
-rw-r--r-- | app/Model/Task.php | 17 | ||||
-rw-r--r-- | app/Model/User.php | 2 | ||||
-rw-r--r-- | app/Schema/Mysql.php | 7 | ||||
-rw-r--r-- | app/Schema/Sqlite.php | 7 | ||||
-rw-r--r-- | app/Templates/config_index.php | 4 | ||||
-rw-r--r-- | jsonrpc.php | 238 | ||||
-rw-r--r-- | phpunit.xml | 2 | ||||
-rwxr-xr-x | scripts/api-client.php | 20 | ||||
-rw-r--r-- | tests/Base.php | 57 | ||||
-rw-r--r-- | tests/functionals/ApiTest.php | 417 | ||||
-rw-r--r-- | tests/units/AclTest.php (renamed from tests/AclTest.php) | 0 | ||||
-rw-r--r-- | tests/units/ActionTaskAssignColorCategoryTest.php (renamed from tests/ActionTaskAssignColorCategoryTest.php) | 0 | ||||
-rw-r--r-- | tests/units/ActionTaskAssignColorUserTest.php (renamed from tests/ActionTaskAssignColorUserTest.php) | 0 | ||||
-rw-r--r-- | tests/units/ActionTaskAssignCurrentUserTest.php (renamed from tests/ActionTaskAssignCurrentUserTest.php) | 0 | ||||
-rw-r--r-- | tests/units/ActionTaskAssignSpecificUserTest.php (renamed from tests/ActionTaskAssignSpecificUserTest.php) | 0 | ||||
-rw-r--r-- | tests/units/ActionTaskCloseTest.php (renamed from tests/ActionTaskCloseTest.php) | 0 | ||||
-rw-r--r-- | tests/units/ActionTaskDuplicateAnotherProjectTest.php (renamed from tests/ActionTaskDuplicateAnotherProjectTest.php) | 0 | ||||
-rw-r--r-- | tests/units/ActionTest.php (renamed from tests/ActionTest.php) | 0 | ||||
-rw-r--r-- | tests/units/Base.php | 57 | ||||
-rw-r--r-- | tests/units/BoardTest.php (renamed from tests/BoardTest.php) | 0 | ||||
-rw-r--r-- | tests/units/CommentTest.php (renamed from tests/CommentTest.php) | 0 | ||||
-rw-r--r-- | tests/units/ProjectTest.php (renamed from tests/ProjectTest.php) | 0 | ||||
-rw-r--r-- | tests/units/TaskTest.php (renamed from tests/TaskTest.php) | 0 | ||||
-rw-r--r-- | vendor/JsonRPC/Client.php | 174 | ||||
-rw-r--r-- | vendor/JsonRPC/Server.php | 301 |
28 files changed, 1285 insertions, 63 deletions
diff --git a/app/Model/Board.php b/app/Model/Board.php index 59a98923..09fc5b50 100644 --- a/app/Model/Board.php +++ b/app/Model/Board.php @@ -99,7 +99,7 @@ class Board extends Base foreach (array('title', 'task_limit') as $field) { foreach ($values[$field] as $column_id => $field_value) { - $this->db->table(self::TABLE)->eq('id', $column_id)->update(array($field => $field_value)); + $this->updateColumn($column_id, array($field => $field_value)); } } @@ -109,6 +109,19 @@ class Board extends Base } /** + * Update a column + * + * @access public + * @param integer $column_id Column id + * @param array $values Form values + * @return boolean + */ + public function updateColumn($column_id, array $values) + { + return $this->db->table(self::TABLE)->eq('id', $column_id)->update($values); + } + + /** * Move a column down, increment the column position value * * @access public diff --git a/app/Model/Category.php b/app/Model/Category.php index 58eba403..f86abe58 100644 --- a/app/Model/Category.php +++ b/app/Model/Category.php @@ -62,6 +62,21 @@ class Category extends Base } /** + * Return all categories for a given project + * + * @access public + * @param integer $project_id Project id + * @return array + */ + public function getAll($project_id) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->asc('name') + ->findAll(); + } + + /** * Create a category * * @access public diff --git a/app/Model/Project.php b/app/Model/Project.php index e1465012..b5716a81 100644 --- a/app/Model/Project.php +++ b/app/Model/Project.php @@ -198,6 +198,18 @@ class Project extends Base } /** + * Get a project by the name + * + * @access public + * @param string $project_name Project name + * @return array + */ + public function getByName($project_name) + { + return $this->db->table(self::TABLE)->eq('name', $project_name)->findOne(); + } + + /** * Fetch project data by using the token * * @access public @@ -505,7 +517,8 @@ class Project extends Base new Validators\Integer('id', t('This value must be an integer')), new Validators\Required('name', t('The project name is required')), new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50), - new Validators\Unique('name', t('This project must be unique'), $this->db->getConnection(), self::TABLE) + new Validators\Unique('name', t('This project must be unique'), $this->db->getConnection(), self::TABLE), + new Validators\Integer('is_active', t('This value must be an integer')) )); return array( diff --git a/app/Model/Task.php b/app/Model/Task.php index 70f1404c..09e2f4e4 100644 --- a/app/Model/Task.php +++ b/app/Model/Task.php @@ -114,6 +114,23 @@ class Task extends Base * @access public * @param integer $project_id Project id * @param array $status List of status id + * @return array + */ + public function getAll($project_id, array $status = array(self::STATUS_OPEN, self::STATUS_CLOSED)) + { + return $this->db + ->table(self::TABLE) + ->eq('project_id', $project_id) + ->in('is_active', $status) + ->findAll(); + } + + /** + * Count all tasks for a given project and status + * + * @access public + * @param integer $project_id Project id + * @param array $status List of status id * @return integer */ public function countByProjectId($project_id, array $status = array(self::STATUS_OPEN, self::STATUS_CLOSED)) diff --git a/app/Model/User.php b/app/Model/User.php index 6804d765..3240d547 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -135,7 +135,7 @@ class User extends Base $result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values); - if ($_SESSION['user']['id'] == $values['id']) { + if (session_id() !== '' && $_SESSION['user']['id'] == $values['id']) { $this->updateSession(); } diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 5c7e256f..ddb2acee 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -2,7 +2,12 @@ namespace Schema; -const VERSION = 18; +const VERSION = 19; + +function version_19($pdo) +{ + $pdo->exec("ALTER TABLE config ADD COLUMN api_token VARCHAR(255) DEFAULT '".\Core\Security::generateToken()."'"); +} function version_18($pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index bfe81c13..438769f0 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -2,7 +2,12 @@ namespace Schema; -const VERSION = 18; +const VERSION = 19; + +function version_19($pdo) +{ + $pdo->exec("ALTER TABLE config ADD COLUMN api_token TEXT DEFAULT '".\Core\Security::generateToken()."'"); +} function version_18($pdo) { diff --git a/app/Templates/config_index.php b/app/Templates/config_index.php index 602e2070..b242d16f 100644 --- a/app/Templates/config_index.php +++ b/app/Templates/config_index.php @@ -46,6 +46,10 @@ <?= t('Webhooks token:') ?> <strong><?= Helper\escape($values['webhooks_token']) ?></strong> </li> + <li> + <?= t('API token:') ?> + <strong><?= Helper\escape($values['api_token']) ?></strong> + </li> <?php if (DB_DRIVER === 'sqlite'): ?> <li> <?= t('Database size:') ?> diff --git a/jsonrpc.php b/jsonrpc.php new file mode 100644 index 00000000..981fefa2 --- /dev/null +++ b/jsonrpc.php @@ -0,0 +1,238 @@ +<?php + +require __DIR__.'/app/common.php'; +require __DIR__.'/vendor/JsonRPC/Server.php'; + +use JsonRPC\Server; +use Model\Project; +use Model\Task; +use Model\User; +use Model\Config; +use Model\Category; +use Model\Comment; +use Model\Subtask; +use Model\Board; +use Model\Action; + +$config = new Config($registry->shared('db'), $registry->shared('event')); +$project = new Project($registry->shared('db'), $registry->shared('event')); +$task = new Task($registry->shared('db'), $registry->shared('event')); +$user = new User($registry->shared('db'), $registry->shared('event')); +$category = new Category($registry->shared('db'), $registry->shared('event')); +$comment = new Comment($registry->shared('db'), $registry->shared('event')); +$subtask = new Subtask($registry->shared('db'), $registry->shared('event')); +$board = new Board($registry->shared('db'), $registry->shared('event')); +$action = new Action($registry->shared('db'), $registry->shared('event')); + +$action->attachEvents(); +$project->attachEvents(); + +$server = new Server; +$server->authentication(array('jsonrpc' => $config->get('api_token'))); + +/** + * Project procedures + */ +$server->register('createProject', function($name) use ($project) { + $values = array('name' => $name); + list($valid,) = $project->validateCreation($values); + return $valid && $project->create($values); +}); + +$server->register('getProjectById', function($project_id) use ($project) { + return $project->getById($project_id); +}); + +$server->register('getProjectByName', function($name) use ($project) { + return $project->getByName($name); +}); + +$server->register('getAllProjects', function() use ($project) { + return $project->getAll(); +}); + +$server->register('updateProject', function(array $values) use ($project) { + list($valid,) = $project->validateModification($values); + return $valid && $project->update($values); +}); + +$server->register('removeProject', function($project_id) use ($project) { + return $project->remove($project_id); +}); + +$server->register('getBoard', function($project_id) use ($board) { + return $board->get($project_id); +}); + +$server->register('getColumns', function($project_id) use ($board) { + return $board->getColumns($project_id); +}); + +$server->register('moveColumnUp', function($project_id, $column_id) use ($board) { + return $board->moveUp($project_id, $column_id); +}); + +$server->register('moveColumnDown', function($project_id, $column_id) use ($board) { + return $board->moveDown($project_id, $column_id); +}); + +$server->register('updateColumn', function($column_id, array $values) use ($board) { + return $board->updateColumn($column_id, $values); +}); + +$server->register('addColumn', function($project_id, array $values) use ($board) { + $values += array('project_id' => $project_id); + return $board->add($values); +}); + +$server->register('removeColumn', function($column_id) use ($board) { + return $board->removeColumn($column_id); +}); + +$server->register('getAllowedUsers', function($project_id) use ($project) { + return $project->getUsersList($project_id, false, false); +}); + +$server->register('revokeUser', function($project_id, $user_id) use ($project) { + return $project->revokeUser($project_id, $user_id); +}); + +$server->register('allowUser', function($project_id, $user_id) use ($project) { + return $project->allowUser($project_id, $user_id); +}); + + +/** + * Task procedures + */ +$server->register('createTask', function(array $values) use ($task) { + list($valid,) = $task->validateCreation($values); + return $valid && $task->create($values) !== false; +}); + +$server->register('getTask', function($task_id) use ($task) { + return $task->getById($task_id); +}); + +$server->register('getAllTasks', function($project_id, array $status) use ($task) { + return $task->getAll($project_id, $status); +}); + +$server->register('updateTask', function($values) use ($task) { + list($valid,) = $task->validateModification($values); + return $valid && $task->update($values); +}); + +$server->register('removeTask', function($task_id) use ($task) { + return $task->remove($task_id); +}); + + +/** + * User procedures + */ +$server->register('createUser', function(array $values) use ($user) { + list($valid,) = $user->validateCreation($values); + return $valid && $user->create($values); +}); + +$server->register('getUser', function($user_id) use ($user) { + return $user->getById($user_id); +}); + +$server->register('getAllUsers', function() use ($user) { + return $user->getAll(); +}); + +$server->register('updateUser', function($values) use ($user) { + list($valid,) = $user->validateModification($values); + return $valid && $user->update($values); +}); + +$server->register('removeUser', function($user_id) use ($user) { + return $user->remove($user_id); +}); + + +/** + * Category procedures + */ +$server->register('createCategory', function(array $values) use ($category) { + list($valid,) = $category->validateCreation($values); + return $valid && $category->create($values); +}); + +$server->register('getCategory', function($category_id) use ($category) { + return $category->getById($category_id); +}); + +$server->register('getAllCategories', function($project_id) use ($category) { + return $category->getAll($project_id); +}); + +$server->register('updateCategory', function($values) use ($category) { + list($valid,) = $category->validateModification($values); + return $valid && $category->update($values); +}); + +$server->register('removeCategory', function($category_id) use ($category) { + return $category->remove($category_id); +}); + + +/** + * Comments procedures + */ +$server->register('createComment', function(array $values) use ($comment) { + list($valid,) = $comment->validateCreation($values); + return $valid && $comment->create($values); +}); + +$server->register('getComment', function($comment_id) use ($comment) { + return $comment->getById($comment_id); +}); + +$server->register('getAllComments', function($task_id) use ($comment) { + return $comment->getAll($task_id); +}); + +$server->register('updateComment', function($values) use ($comment) { + list($valid,) = $comment->validateModification($values); + return $valid && $comment->update($values); +}); + +$server->register('removeComment', function($comment_id) use ($comment) { + return $comment->remove($comment_id); +}); + + +/** + * Subtask procedures + */ +$server->register('createSubtask', function(array $values) use ($subtask) { + list($valid,) = $subtask->validate($values); + return $valid && $subtask->create($values); +}); + +$server->register('getSubtask', function($subtask_id) use ($subtask) { + return $subtask->getById($subtask_id); +}); + +$server->register('getAllSubtasks', function($task_id) use ($subtask) { + return $subtask->getAll($task_id); +}); + +$server->register('updateSubtask', function($values) use ($subtask) { + list($valid,) = $subtask->validate($values); + return $valid && $subtask->update($values); +}); + +$server->register('removeSubtask', function($subtask_id) use ($subtask) { + return $subtask->remove($subtask_id); +}); + + +/** + * Parse incoming requests + */ +echo $server->execute(); diff --git a/phpunit.xml b/phpunit.xml index 0a1e5c45..5c4ce58c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,7 @@ <phpunit> <testsuites> <testsuite name="Kanboard"> - <directory>tests</directory> + <directory>tests/units</directory> </testsuite> </testsuites> </phpunit>
\ No newline at end of file diff --git a/scripts/api-client.php b/scripts/api-client.php new file mode 100755 index 00000000..0fb45117 --- /dev/null +++ b/scripts/api-client.php @@ -0,0 +1,20 @@ +#!/usr/bin/env php +<?php + +require 'vendor/JsonRPC/Client.php'; + +if ($argc !== 3) { + die('Usage: '.$argv[0].' <url> <token>'.PHP_EOL); +} + +$client = new JsonRPC\Client($argv[1], 5, true); +$client->authentication('jsonrpc', $argv[2]); + + +$client->createProject('Test API'); + + +$r = $client->getAllProjects(); + +var_dump($r); + diff --git a/tests/Base.php b/tests/Base.php deleted file mode 100644 index d4065982..00000000 --- a/tests/Base.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php - -if (version_compare(PHP_VERSION, '5.5.0', '<')) { - require __DIR__.'/../vendor/password.php'; -} - -require_once __DIR__.'/../app/Core/Security.php'; - -require_once __DIR__.'/../vendor/PicoDb/Database.php'; -require_once __DIR__.'/../app/Schema/Sqlite.php'; - -require_once __DIR__.'/../app/Core/Listener.php'; -require_once __DIR__.'/../app/Core/Event.php'; -require_once __DIR__.'/../app/Core/Translator.php'; -require_once __DIR__.'/../app/translator.php'; - -require_once __DIR__.'/../app/Model/Base.php'; -require_once __DIR__.'/../app/Model/Task.php'; -require_once __DIR__.'/../app/Model/Acl.php'; -require_once __DIR__.'/../app/Model/Comment.php'; -require_once __DIR__.'/../app/Model/Project.php'; -require_once __DIR__.'/../app/Model/User.php'; -require_once __DIR__.'/../app/Model/Board.php'; -require_once __DIR__.'/../app/Model/Action.php'; -require_once __DIR__.'/../app/Model/Category.php'; - -require_once __DIR__.'/../app/Action/Base.php'; -require_once __DIR__.'/../app/Action/TaskClose.php'; -require_once __DIR__.'/../app/Action/TaskAssignSpecificUser.php'; -require_once __DIR__.'/../app/Action/TaskAssignColorUser.php'; -require_once __DIR__.'/../app/Action/TaskAssignColorCategory.php'; -require_once __DIR__.'/../app/Action/TaskAssignCurrentUser.php'; -require_once __DIR__.'/../app/Action/TaskDuplicateAnotherProject.php'; - -abstract class Base extends PHPUnit_Framework_TestCase -{ - public function setUp() - { - $this->db = $this->getDbConnection(); - $this->event = new \Core\Event; - } - - public function getDbConnection() - { - $db = new \PicoDb\Database(array( - 'driver' => 'sqlite', - 'filename' => ':memory:' - )); - - if ($db->schema()->check(\Schema\VERSION)) { - return $db; - } - else { - die('Unable to migrate database schema!'); - } - } -} diff --git a/tests/functionals/ApiTest.php b/tests/functionals/ApiTest.php new file mode 100644 index 00000000..89d525a2 --- /dev/null +++ b/tests/functionals/ApiTest.php @@ -0,0 +1,417 @@ +<?php + +require_once __DIR__.'/../../vendor/JsonRPC/Client.php'; + +class Api extends PHPUnit_Framework_TestCase +{ + const URL = 'http://localhost:8000/jsonrpc.php'; + const KEY = '19ffd9709d03ce50675c3a43d1c49c1ac207f4bc45f06c5b2701fbdf8929'; + + private $client; + + public function setUp() + { + $this->client = new JsonRPC\Client(self::URL, 5, true); + $this->client->authentication('jsonrpc', self::KEY); + + $pdo = new PDO('sqlite:data/db.sqlite'); + $pdo->exec('UPDATE config SET api_token="'.self::KEY.'"'); + } + + public function testRemoveAll() + { + $projects = $this->client->getAllProjects(); + + if ($projects) { + foreach ($projects as $project) { + $this->client->removeProject($project['id']); + } + } + } + + public function testCreateProject() + { + $this->assertTrue($this->client->createProject('API test')); + } + + public function testGetProjectById() + { + $project = $this->client->getProjectById(1); + $this->assertNotEmpty($project); + $this->assertEquals(1, $project['id']); + } + + public function testUpdateProject() + { + $project = $this->client->getProjectById(1); + $this->assertNotEmpty($project); + $this->assertTrue($this->client->updateProject(array('id' => 1, 'name' => 'API test 2', 'is_active' => 0))); + + $project = $this->client->getProjectById(1); + $this->assertEquals('API test 2', $project['name']); + $this->assertEquals(0, $project['is_active']); + + $this->assertTrue($this->client->updateProject(array('id' => 1, 'name' => 'API test', 'is_active' => 1))); + + $project = $this->client->getProjectById(1); + $this->assertEquals('API test', $project['name']); + $this->assertEquals(1, $project['is_active']); + } + + public function testGetBoard() + { + $board = $this->client->getBoard(1); + $this->assertTrue(is_array($board)); + $this->assertEquals(4, count($board)); + } + + public function testGetColumns() + { + $columns = $this->client->getColumns(1); + $this->assertTrue(is_array($columns)); + $this->assertEquals(4, count($columns)); + $this->assertEquals('Done', $columns[3]['title']); + } + + public function testMoveColumnUp() + { + $this->assertTrue($this->client->moveColumnUp(1, 4)); + + $columns = $this->client->getColumns(1); + $this->assertTrue(is_array($columns)); + $this->assertEquals('Done', $columns[2]['title']); + $this->assertEquals('Work in progress', $columns[3]['title']); + } + + public function testMoveColumnDown() + { + $this->assertTrue($this->client->moveColumnDown(1, 4)); + + $columns = $this->client->getColumns(1); + $this->assertTrue(is_array($columns)); + $this->assertEquals('Work in progress', $columns[2]['title']); + $this->assertEquals('Done', $columns[3]['title']); + } + + public function testUpdateColumn() + { + $this->assertTrue($this->client->updateColumn(4, array('title' => 'Boo', 'task_limit' => 2))); + + $columns = $this->client->getColumns(1); + $this->assertTrue(is_array($columns)); + $this->assertEquals('Boo', $columns[3]['title']); + $this->assertEquals(2, $columns[3]['task_limit']); + } + + public function testAddColumn() + { + $this->assertTrue($this->client->addColumn(1, array('title' => 'New column'))); + + $columns = $this->client->getColumns(1); + $this->assertTrue(is_array($columns)); + $this->assertEquals(5, count($columns)); + $this->assertEquals('New column', $columns[4]['title']); + } + + public function testRemoveColumn() + { + $this->assertTrue($this->client->removeColumn(5)); + + $columns = $this->client->getColumns(1); + $this->assertTrue(is_array($columns)); + $this->assertEquals(4, count($columns)); + } + + public function testCreateTask() + { + $task = array( + 'title' => 'Task #1', + 'color_id' => 'blue', + 'owner_id' => 1, + 'project_id' => 1, + 'column_id' => 2, + ); + + $this->assertTrue($this->client->createTask($task)); + + $task = array( + 'title' => 'Task #1', + 'color_id' => 'blue', + 'owner_id' => 1, + ); + + $this->assertFalse($this->client->createTask($task)); + } + + public function testGetTask() + { + $task = $this->client->getTask(1); + + $this->assertNotFalse($task); + $this->assertTrue(is_array($task)); + $this->assertEquals('Task #1', $task['title']); + } + + public function testGetAllTasks() + { + $tasks = $this->client->getAllTasks(1, array(1)); + + $this->assertNotFalse($tasks); + $this->assertTrue(is_array($tasks)); + $this->assertEquals('Task #1', $tasks[0]['title']); + + $tasks = $this->client->getAllTasks(2, array(1, 2)); + + $this->assertNotFalse($tasks); + $this->assertTrue(is_array($tasks)); + $this->assertEmpty($tasks); + } + + public function testUpdateTask() + { + $task = $this->client->getTask(1); + $task['color_id'] = 'green'; + $task['column_id'] = 1; + $task['description'] = 'test'; + + $this->assertTrue($this->client->updateTask($task)); + } + + public function testRemoveTask() + { + $this->assertTrue($this->client->removeTask(1)); + } + + public function testRemoveUsers() + { + $users = $this->client->getAllUsers(); + $this->assertNotFalse($users); + $this->assertNotEmpty($users); + + foreach ($users as $user) { + if ($user['id'] > 1) { + $this->assertTrue($this->client->removeUser($user['id'])); + } + } + } + + public function testCreateUser() + { + $user = array( + 'username' => 'toto', + 'name' => 'Toto', + 'password' => '123456', + 'confirmation' => '123456', + ); + + $this->assertTrue($this->client->createUser($user)); + + $user = array( + 'username' => 'titi', + 'name' => 'Titi', + 'password' => '123456', + 'confirmation' => '789', + ); + + $this->assertFalse($this->client->createUser($user)); + } + + public function testGetUser() + { + $user = $this->client->getUser(2); + + $this->assertNotFalse($user); + $this->assertTrue(is_array($user)); + $this->assertEquals('toto', $user['username']); + } + + public function testUpdateUser() + { + $user = $this->client->getUser(2); + $user['username'] = 'titi'; + $user['name'] = 'Titi'; + unset($user['password']); + + $this->assertTrue($this->client->updateUser($user)); + + $user = $this->client->getUser(2); + + $this->assertNotFalse($user); + $this->assertTrue(is_array($user)); + $this->assertEquals('titi', $user['username']); + $this->assertEquals('Titi', $user['name']); + } + + public function testGetAllowedUsers() + { + $users = $this->client->getAllowedUsers(1); + $this->assertNotFalse($users); + $this->assertEquals(array(1 => 'admin', 2 => 'titi'), $users); + } + + public function testAllowedUser() + { + $this->assertTrue($this->client->allowUser(1, 2)); + + $users = $this->client->getAllowedUsers(1); + $this->assertNotFalse($users); + $this->assertEquals(array(2 => 'titi'), $users); + } + + public function testRevokeUser() + { + $this->assertTrue($this->client->revokeUser(1, 2)); + + $users = $this->client->getAllowedUsers(1); + $this->assertNotFalse($users); + $this->assertEquals(array(1 => 'admin', 2 => 'titi'), $users); + } + + public function testCreateComment() + { + $task = array( + 'title' => 'Task with comment', + 'color_id' => 'red', + 'owner_id' => 1, + 'project_id' => 1, + 'column_id' => 1, + ); + + $this->assertTrue($this->client->createTask($task)); + + $comment = array( + 'task_id' => 1, + 'user_id' => 2, + 'comment' => 'boo', + ); + + $this->assertTrue($this->client->createComment($comment)); + } + + public function testGetComment() + { + $comment = $this->client->getComment(1); + $this->assertNotFalse($comment); + $this->assertNotEmpty($comment); + $this->assertEquals(1, $comment['task_id']); + $this->assertEquals(2, $comment['user_id']); + $this->assertEquals('boo', $comment['comment']); + } + + public function testUpdateComment() + { + $comment = $this->client->getComment(1); + $comment['comment'] = 'test'; + + $this->assertTrue($this->client->updateComment($comment)); + + $comment = $this->client->getComment(1); + $this->assertEquals('test', $comment['comment']); + } + + public function testGetAllComments() + { + $comment = array( + 'task_id' => 1, + 'user_id' => 1, + 'comment' => 'blabla', + ); + + $this->assertTrue($this->client->createComment($comment)); + + $comments = $this->client->getAllComments(1); + $this->assertNotFalse($comments); + $this->assertNotEmpty($comments); + $this->assertTrue(is_array($comments)); + $this->assertEquals(2, count($comments)); + } + + public function testRemoveComment() + { + $this->assertTrue($this->client->removeComment(1)); + + $comments = $this->client->getAllComments(1); + $this->assertNotFalse($comments); + $this->assertNotEmpty($comments); + $this->assertTrue(is_array($comments)); + $this->assertEquals(1, count($comments)); + } + + public function testCreateSubtask() + { + $subtask = array( + 'task_id' => 1, + 'title' => 'subtask #1', + ); + + $this->assertTrue($this->client->createSubtask($subtask)); + } + + public function testGetSubtask() + { + $subtask = $this->client->getSubtask(1); + $this->assertNotFalse($subtask); + $this->assertNotEmpty($subtask); + $this->assertEquals(1, $subtask['task_id']); + $this->assertEquals(0, $subtask['user_id']); + $this->assertEquals('subtask #1', $subtask['title']); + } + + public function testUpdateSubtask() + { + $subtask = $this->client->getSubtask(1); + $subtask['title'] = 'test'; + + $this->assertTrue($this->client->updateSubtask($subtask)); + + $subtask = $this->client->getSubtask(1); + $this->assertEquals('test', $subtask['title']); + } + + public function testGetAllSubtasks() + { + $subtask = array( + 'task_id' => 1, + 'user_id' => 2, + 'title' => 'Subtask #2', + ); + + $this->assertTrue($this->client->createSubtask($subtask)); + + $subtasks = $this->client->getAllSubtasks(1); + $this->assertNotFalse($subtasks); + $this->assertNotEmpty($subtasks); + $this->assertTrue(is_array($subtasks)); + $this->assertEquals(2, count($subtasks)); + } + + public function testRemoveSubtask() + { + $this->assertTrue($this->client->removeSubtask(1)); + + $subtasks = $this->client->getAllSubtasks(1); + $this->assertNotFalse($subtasks); + $this->assertNotEmpty($subtasks); + $this->assertTrue(is_array($subtasks)); + $this->assertEquals(1, count($subtasks)); + } +/* + public function testAutomaticActions() + { + $task = array( + 'title' => 'Task #1', + 'color_id' => 'blue', + 'owner_id' => 0, + 'project_id' => 1, + 'column_id' => 1, + ); + + $this->assertTrue($this->client->createTask($task)); + + $tasks = $this->client->getAllTasks(1, array(1)); + $task = $tasks[count($tasks) - 1]; + $task['column_id'] = 3; + + $this->assertTrue($this->client->updateTask($task)); + }*/ +} diff --git a/tests/AclTest.php b/tests/units/AclTest.php index a2c1c111..a2c1c111 100644 --- a/tests/AclTest.php +++ b/tests/units/AclTest.php diff --git a/tests/ActionTaskAssignColorCategoryTest.php b/tests/units/ActionTaskAssignColorCategoryTest.php index 18b4311e..18b4311e 100644 --- a/tests/ActionTaskAssignColorCategoryTest.php +++ b/tests/units/ActionTaskAssignColorCategoryTest.php diff --git a/tests/ActionTaskAssignColorUserTest.php b/tests/units/ActionTaskAssignColorUserTest.php index 04e3172f..04e3172f 100644 --- a/tests/ActionTaskAssignColorUserTest.php +++ b/tests/units/ActionTaskAssignColorUserTest.php diff --git a/tests/ActionTaskAssignCurrentUserTest.php b/tests/units/ActionTaskAssignCurrentUserTest.php index 0780b603..0780b603 100644 --- a/tests/ActionTaskAssignCurrentUserTest.php +++ b/tests/units/ActionTaskAssignCurrentUserTest.php diff --git a/tests/ActionTaskAssignSpecificUserTest.php b/tests/units/ActionTaskAssignSpecificUserTest.php index 3a919978..3a919978 100644 --- a/tests/ActionTaskAssignSpecificUserTest.php +++ b/tests/units/ActionTaskAssignSpecificUserTest.php diff --git a/tests/ActionTaskCloseTest.php b/tests/units/ActionTaskCloseTest.php index 0c864f42..0c864f42 100644 --- a/tests/ActionTaskCloseTest.php +++ b/tests/units/ActionTaskCloseTest.php diff --git a/tests/ActionTaskDuplicateAnotherProjectTest.php b/tests/units/ActionTaskDuplicateAnotherProjectTest.php index 0b2e4bd5..0b2e4bd5 100644 --- a/tests/ActionTaskDuplicateAnotherProjectTest.php +++ b/tests/units/ActionTaskDuplicateAnotherProjectTest.php diff --git a/tests/ActionTest.php b/tests/units/ActionTest.php index 2eb12784..2eb12784 100644 --- a/tests/ActionTest.php +++ b/tests/units/ActionTest.php diff --git a/tests/units/Base.php b/tests/units/Base.php new file mode 100644 index 00000000..1f8109ed --- /dev/null +++ b/tests/units/Base.php @@ -0,0 +1,57 @@ +<?php + +if (version_compare(PHP_VERSION, '5.5.0', '<')) { + require __DIR__.'/../../vendor/password.php'; +} + +require_once __DIR__.'/../../app/Core/Security.php'; + +require_once __DIR__.'/../../vendor/PicoDb/Database.php'; +require_once __DIR__.'/../../app/Schema/Sqlite.php'; + +require_once __DIR__.'/../../app/Core/Listener.php'; +require_once __DIR__.'/../../app/Core/Event.php'; +require_once __DIR__.'/../../app/Core/Translator.php'; +require_once __DIR__.'/../../app/translator.php'; + +require_once __DIR__.'/../../app/Model/Base.php'; +require_once __DIR__.'/../../app/Model/Task.php'; +require_once __DIR__.'/../../app/Model/Acl.php'; +require_once __DIR__.'/../../app/Model/Comment.php'; +require_once __DIR__.'/../../app/Model/Project.php'; +require_once __DIR__.'/../../app/Model/User.php'; +require_once __DIR__.'/../../app/Model/Board.php'; +require_once __DIR__.'/../../app/Model/Action.php'; +require_once __DIR__.'/../../app/Model/Category.php'; + +require_once __DIR__.'/../../app/Action/Base.php'; +require_once __DIR__.'/../../app/Action/TaskClose.php'; +require_once __DIR__.'/../../app/Action/TaskAssignSpecificUser.php'; +require_once __DIR__.'/../../app/Action/TaskAssignColorUser.php'; +require_once __DIR__.'/../../app/Action/TaskAssignColorCategory.php'; +require_once __DIR__.'/../../app/Action/TaskAssignCurrentUser.php'; +require_once __DIR__.'/../../app/Action/TaskDuplicateAnotherProject.php'; + +abstract class Base extends PHPUnit_Framework_TestCase +{ + public function setUp() + { + $this->db = $this->getDbConnection(); + $this->event = new \Core\Event; + } + + public function getDbConnection() + { + $db = new \PicoDb\Database(array( + 'driver' => 'sqlite', + 'filename' => ':memory:' + )); + + if ($db->schema()->check(\Schema\VERSION)) { + return $db; + } + else { + die('Unable to migrate database schema!'); + } + } +} diff --git a/tests/BoardTest.php b/tests/units/BoardTest.php index d5686b3f..d5686b3f 100644 --- a/tests/BoardTest.php +++ b/tests/units/BoardTest.php diff --git a/tests/CommentTest.php b/tests/units/CommentTest.php index 46f05abc..46f05abc 100644 --- a/tests/CommentTest.php +++ b/tests/units/CommentTest.php diff --git a/tests/ProjectTest.php b/tests/units/ProjectTest.php index 5ca8177c..5ca8177c 100644 --- a/tests/ProjectTest.php +++ b/tests/units/ProjectTest.php diff --git a/tests/TaskTest.php b/tests/units/TaskTest.php index da7e6a70..da7e6a70 100644 --- a/tests/TaskTest.php +++ b/tests/units/TaskTest.php diff --git a/vendor/JsonRPC/Client.php b/vendor/JsonRPC/Client.php new file mode 100644 index 00000000..bbdb7200 --- /dev/null +++ b/vendor/JsonRPC/Client.php @@ -0,0 +1,174 @@ +<?php + +namespace JsonRPC; + +/** + * JsonRPC client class + * + * @package JsonRPC + * @author Frderic Guillot + * @license Unlicense http://unlicense.org/ + */ +class Client +{ + /** + * URL of the server + * + * @access private + * @var string + */ + private $url; + + /** + * HTTP client timeout + * + * @access private + * @var integer + */ + private $timeout; + + /** + * Debug flag + * + * @access private + * @var bool + */ + private $debug; + + /** + * Username for authentication + * + * @access private + * @var string + */ + private $username; + + /** + * Password for authentication + * + * @access private + * @var string + */ + private $password; + + /** + * Default HTTP headers to send to the server + * + * @access private + * @var array + */ + private $headers = array( + 'Connection: close', + 'Content-Type: application/json', + 'Accept: application/json' + ); + + /** + * Constructor + * + * @access public + * @param string $url Server URL + * @param integer $timeout Server URL + * @param bool $debug Debug flag + * @param array $headers Custom HTTP headers + */ + public function __construct($url, $timeout = 5, $debug = false, $headers = array()) + { + $this->url = $url; + $this->timeout = $timeout; + $this->debug = $debug; + $this->headers = array_merge($this->headers, $headers); + } + + /** + * Automatic mapping of procedures + * + * @access public + * @param string $method Procedure name + * @param array $params Procedure arguments + * @return mixed + */ + public function __call($method, $params) + { + return $this->execute($method, $params); + } + + /** + * Set authentication parameters + * + * @access public + * @param string $username Username + * @param string $password Password + */ + public function authentication($username, $password) + { + $this->username = $username; + $this->password = $password; + } + + /** + * Execute + * + * @access public + * @param string $procedure Procedure name + * @param array $params Procedure arguments + * @return mixed + */ + public function execute($procedure, array $params = array()) + { + $id = mt_rand(); + + $payload = array( + 'jsonrpc' => '2.0', + 'method' => $procedure, + 'id' => $id + ); + + if (! empty($params)) { + $payload['params'] = $params; + } + + $result = $this->doRequest($payload); + + if (isset($result['id']) && $result['id'] == $id && array_key_exists('result', $result)) { + return $result['result']; + } + else if ($this->debug && isset($result['error'])) { + print_r($result['error']); + } + + return null; + } + + /** + * Do the HTTP request + * + * @access public + * @param string $payload Data to send + */ + public function doRequest($payload) + { + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $this->url); + curl_setopt($ch, CURLOPT_HEADER, false); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_USERAGENT, 'JSON-RPC PHP Client'); + curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); + + if ($this->username && $this->password) { + curl_setopt($ch, CURLOPT_USERPWD, $this->username.':'.$this->password); + } + + $result = curl_exec($ch); + $response = json_decode($result, true); + + curl_close($ch); + + return is_array($response) ? $response : array(); + } +} diff --git a/vendor/JsonRPC/Server.php b/vendor/JsonRPC/Server.php new file mode 100644 index 00000000..93d46cdb --- /dev/null +++ b/vendor/JsonRPC/Server.php @@ -0,0 +1,301 @@ +<?php + +namespace JsonRPC; + +use ReflectionFunction; +use Closure; + +/** + * JsonRPC server class + * + * @package JsonRPC + * @author Frderic Guillot + * @license Unlicense http://unlicense.org/ + */ +class Server +{ + /** + * Data received from the client + * + * @access private + * @var string + */ + private $payload; + + /** + * List of procedures + * + * @static + * @access private + * @var array + */ + static private $procedures = array(); + + /** + * Constructor + * + * @access public + * @param string $payload Client data + */ + public function __construct($payload = '') + { + $this->payload = $payload; + } + + /** + * IP based client restrictions + * + * Return an HTTP error 403 if the client is not allowed + * + * @access public + * @param array $hosts List of hosts + */ + public function allowHosts(array $hosts) { + + if (! in_array($_SERVER['REMOTE_ADDR'], $hosts)) { + + header('Content-Type: application/json'); + header('HTTP/1.0 403 Forbidden'); + echo '["Access Forbidden"]'; + exit; + } + } + + /** + * HTTP Basic authentication + * + * Return an HTTP error 401 if the client is not allowed + * + * @access public + * @param array $users Map of username/password + */ + public function authentication(array $users) + { + // OVH workaround + if (isset($_SERVER['REMOTE_USER'])) { + list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':', base64_decode(substr($_SERVER['REMOTE_USER'], 6))); + } + + if (! isset($_SERVER['PHP_AUTH_USER']) || + ! isset($users[$_SERVER['PHP_AUTH_USER']]) || + $users[$_SERVER['PHP_AUTH_USER']] !== $_SERVER['PHP_AUTH_PW']) { + + header('WWW-Authenticate: Basic realm="JsonRPC"'); + header('Content-Type: application/json'); + header('HTTP/1.0 401 Unauthorized'); + echo '["Authentication failed"]'; + exit; + } + } + + /** + * Register a new procedure + * + * @access public + * @param string $name Procedure name + * @param closure $callback Callback + */ + public function register($name, Closure $callback) + { + self::$procedures[$name] = $callback; + } + + /** + * Unregister a procedure + * + * @access public + * @param string $name Procedure name + */ + public function unregister($name) + { + if (isset(self::$procedures[$name])) { + unset(self::$procedures[$name]); + } + } + + /** + * Unregister all procedures + * + * @access public + */ + public function unregisterAll() + { + self::$procedures = array(); + } + + /** + * Return the response to the client + * + * @access public + * @param array $data Data to send to the client + * @param array $payload Incoming data + * @return string + */ + public function getResponse(array $data, array $payload = array()) + { + if (! array_key_exists('id', $payload)) { + return ''; + } + + $response = array( + 'jsonrpc' => '2.0', + 'id' => $payload['id'] + ); + + $response = array_merge($response, $data); + + @header('Content-Type: application/json'); + return json_encode($response); + } + + /** + * Map arguments to the procedure + * + * @access public + * @param array $request_params Incoming arguments + * @param array $method_params Procedure arguments + * @param array $params Arguments to pass to the callback + * @return bool + */ + public function mapParameters(array $request_params, array $method_params, array &$params) + { + // Positional parameters + if (array_keys($request_params) === range(0, count($request_params) - 1)) { + + if (count($request_params) !== count($method_params)) return false; + $params = $request_params; + + return true; + } + + // Named parameters + foreach ($method_params as $p) { + + $name = $p->getName(); + + if (isset($request_params[$name])) { + $params[$name] = $request_params[$name]; + } + else if ($p->isDefaultValueAvailable()) { + continue; + } + else { + return false; + } + } + + return true; + } + + /** + * Parse incoming requests + * + * @access public + * @return string + */ + public function execute() + { + // Parse payload + if (empty($this->payload)) { + $this->payload = file_get_contents('php://input'); + } + + if (is_string($this->payload)) { + $this->payload = json_decode($this->payload, true); + } + + // Check JSON format + if (! is_array($this->payload)) { + + return $this->getResponse(array( + 'error' => array( + 'code' => -32700, + 'message' => 'Parse error' + )), + array('id' => null) + ); + } + + // Handle batch request + if (array_keys($this->payload) === range(0, count($this->payload) - 1)) { + + $responses = array(); + + foreach ($this->payload as $payload) { + + if (! is_array($payload)) { + + $responses[] = $this->getResponse(array( + 'error' => array( + 'code' => -32600, + 'message' => 'Invalid Request' + )), + array('id' => null) + ); + } + else { + + $server = new Server($payload); + $response = $server->execute(); + + if ($response) $responses[] = $response; + } + } + + return empty($responses) ? '' : '['.implode(',', $responses).']'; + } + + // Check JSON-RPC format + if (! isset($this->payload['jsonrpc']) || + ! isset($this->payload['method']) || + ! is_string($this->payload['method']) || + $this->payload['jsonrpc'] !== '2.0' || + (isset($this->payload['params']) && ! is_array($this->payload['params']))) { + + return $this->getResponse(array( + 'error' => array( + 'code' => -32600, + 'message' => 'Invalid Request' + )), + array('id' => null) + ); + } + + // Procedure not found + if (! isset(self::$procedures[$this->payload['method']])) { + + return $this->getResponse(array( + 'error' => array( + 'code' => -32601, + 'message' => 'Method not found' + )), + $this->payload + ); + } + + $callback = self::$procedures[$this->payload['method']]; + $params = array(); + + $reflection = new ReflectionFunction($callback); + + if (isset($this->payload['params'])) { + + $parameters = $reflection->getParameters(); + + if (! $this->mapParameters($this->payload['params'], $parameters, $params)) { + + return $this->getResponse(array( + 'error' => array( + 'code' => -32602, + 'message' => 'Invalid params' + )), + $this->payload + ); + } + } + + $result = $reflection->invokeArgs($params); + + return $this->getResponse(array('result' => $result), $this->payload); + } +} |