diff --git a/app/Model/Board.php b/app/Model/Board.php
index 59a98923a..09fc5b502 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));
}
}
@@ -108,6 +108,19 @@ class Board extends Base
return true;
}
+ /**
+ * 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
*
diff --git a/app/Model/Category.php b/app/Model/Category.php
index 58eba403c..f86abe58a 100644
--- a/app/Model/Category.php
+++ b/app/Model/Category.php
@@ -61,6 +61,21 @@ class Category extends Base
return $prepend + $listing;
}
+ /**
+ * 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
*
diff --git a/app/Model/Project.php b/app/Model/Project.php
index e14650121..b5716a81d 100644
--- a/app/Model/Project.php
+++ b/app/Model/Project.php
@@ -197,6 +197,18 @@ class Project extends Base
return $this->db->table(self::TABLE)->eq('id', $project_id)->findOne();
}
+ /**
+ * 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
*
@@ -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 70f1404c0..09e2f4e49 100644
--- a/app/Model/Task.php
+++ b/app/Model/Task.php
@@ -108,6 +108,23 @@ class Task extends Base
}
}
+ /**
+ * 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 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
*
diff --git a/app/Model/User.php b/app/Model/User.php
index 6804d7653..3240d5475 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 5c7e256fd..ddb2acee8 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 bfe81c136..438769f08 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 602e2070e..b242d16ff 100644
--- a/app/Templates/config_index.php
+++ b/app/Templates/config_index.php
@@ -46,6 +46,10 @@
= t('Webhooks token:') ?>
= Helper\escape($values['webhooks_token']) ?>
+
+ = t('API token:') ?>
+ = Helper\escape($values['api_token']) ?>
+
= t('Database size:') ?>
diff --git a/jsonrpc.php b/jsonrpc.php
new file mode 100644
index 000000000..981fefa27
--- /dev/null
+++ b/jsonrpc.php
@@ -0,0 +1,238 @@
+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 0a1e5c45c..5c4ce58cf 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,7 +1,7 @@
- tests
+ tests/units
\ No newline at end of file
diff --git a/scripts/api-client.php b/scripts/api-client.php
new file mode 100755
index 000000000..0fb451173
--- /dev/null
+++ b/scripts/api-client.php
@@ -0,0 +1,20 @@
+#!/usr/bin/env php
+ '.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 d40659826..000000000
--- a/tests/Base.php
+++ /dev/null
@@ -1,57 +0,0 @@
-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 000000000..89d525a2f
--- /dev/null
+++ b/tests/functionals/ApiTest.php
@@ -0,0 +1,417 @@
+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
similarity index 100%
rename from tests/AclTest.php
rename to tests/units/AclTest.php
diff --git a/tests/ActionTaskAssignColorCategoryTest.php b/tests/units/ActionTaskAssignColorCategoryTest.php
similarity index 100%
rename from tests/ActionTaskAssignColorCategoryTest.php
rename to tests/units/ActionTaskAssignColorCategoryTest.php
diff --git a/tests/ActionTaskAssignColorUserTest.php b/tests/units/ActionTaskAssignColorUserTest.php
similarity index 100%
rename from tests/ActionTaskAssignColorUserTest.php
rename to tests/units/ActionTaskAssignColorUserTest.php
diff --git a/tests/ActionTaskAssignCurrentUserTest.php b/tests/units/ActionTaskAssignCurrentUserTest.php
similarity index 100%
rename from tests/ActionTaskAssignCurrentUserTest.php
rename to tests/units/ActionTaskAssignCurrentUserTest.php
diff --git a/tests/ActionTaskAssignSpecificUserTest.php b/tests/units/ActionTaskAssignSpecificUserTest.php
similarity index 100%
rename from tests/ActionTaskAssignSpecificUserTest.php
rename to tests/units/ActionTaskAssignSpecificUserTest.php
diff --git a/tests/ActionTaskCloseTest.php b/tests/units/ActionTaskCloseTest.php
similarity index 100%
rename from tests/ActionTaskCloseTest.php
rename to tests/units/ActionTaskCloseTest.php
diff --git a/tests/ActionTaskDuplicateAnotherProjectTest.php b/tests/units/ActionTaskDuplicateAnotherProjectTest.php
similarity index 100%
rename from tests/ActionTaskDuplicateAnotherProjectTest.php
rename to tests/units/ActionTaskDuplicateAnotherProjectTest.php
diff --git a/tests/ActionTest.php b/tests/units/ActionTest.php
similarity index 100%
rename from tests/ActionTest.php
rename to tests/units/ActionTest.php
diff --git a/tests/units/Base.php b/tests/units/Base.php
new file mode 100644
index 000000000..1f8109ed1
--- /dev/null
+++ b/tests/units/Base.php
@@ -0,0 +1,57 @@
+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
similarity index 100%
rename from tests/BoardTest.php
rename to tests/units/BoardTest.php
diff --git a/tests/CommentTest.php b/tests/units/CommentTest.php
similarity index 100%
rename from tests/CommentTest.php
rename to tests/units/CommentTest.php
diff --git a/tests/ProjectTest.php b/tests/units/ProjectTest.php
similarity index 100%
rename from tests/ProjectTest.php
rename to tests/units/ProjectTest.php
diff --git a/tests/TaskTest.php b/tests/units/TaskTest.php
similarity index 100%
rename from tests/TaskTest.php
rename to tests/units/TaskTest.php
diff --git a/vendor/JsonRPC/Client.php b/vendor/JsonRPC/Client.php
new file mode 100644
index 000000000..bbdb72001
--- /dev/null
+++ b/vendor/JsonRPC/Client.php
@@ -0,0 +1,174 @@
+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 000000000..93d46cdb7
--- /dev/null
+++ b/vendor/JsonRPC/Server.php
@@ -0,0 +1,301 @@
+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);
+ }
+}