diff options
author | Frederic Guillot <fred@kanboard.net> | 2016-01-22 21:23:12 -0500 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2016-01-22 21:23:12 -0500 |
commit | ad8fcf035ab92d8cd06179959000b9a1681b1505 (patch) | |
tree | 80f4e35b16c1c1a6d4c473983bc6cb62b9519494 | |
parent | f27bcec2d92af83ee7205c84954cda9d7b2fc55d (diff) |
Add new API procedures for groups, roles and project permissions
26 files changed, 1122 insertions, 266 deletions
@@ -6,6 +6,7 @@ New features: * Forgot Password * Add dropdown menu on each board column title to close all tasks * Add Malay language +* Add new API procedures for groups, roles and project permissions Improvements: diff --git a/app/Api/App.php b/app/Api/App.php index d082bcfb..635f1ce2 100644 --- a/app/Api/App.php +++ b/app/Api/App.php @@ -34,4 +34,14 @@ class App extends \Kanboard\Core\Base { return $this->color->getList(); } + + public function getApplicationRoles() + { + return $this->role->getApplicationRoles(); + } + + public function getProjectRoles() + { + return $this->role->getProjectRoles(); + } } diff --git a/app/Api/Auth.php b/app/Api/Auth.php index a9d1617c..c7c5298c 100644 --- a/app/Api/Auth.php +++ b/app/Api/Auth.php @@ -23,7 +23,7 @@ class Auth extends Base */ public function checkCredentials($username, $password, $class, $method) { - $this->container['dispatcher']->dispatch('app.bootstrap'); + $this->dispatcher->dispatch('app.bootstrap'); if ($this->isUserAuthenticated($username, $password)) { $this->checkProcedurePermission(true, $method); diff --git a/app/Api/File.php b/app/Api/File.php index be415ecb..269803e1 100644 --- a/app/Api/File.php +++ b/app/Api/File.php @@ -32,14 +32,18 @@ class File extends \Kanboard\Core\Base } } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); + return ''; } - - return ''; } public function createFile($project_id, $task_id, $filename, $blob) { - return $this->file->uploadContent($project_id, $task_id, $filename, $blob); + try { + return $this->file->uploadContent($project_id, $task_id, $filename, $blob); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + return false; + } } public function removeFile($file_id) diff --git a/app/Api/Group.php b/app/Api/Group.php new file mode 100644 index 00000000..a1e0a73d --- /dev/null +++ b/app/Api/Group.php @@ -0,0 +1,49 @@ +<?php + +namespace Kanboard\Api; + +/** + * Group API controller + * + * @package api + * @author Frederic Guillot + */ +class Group extends \Kanboard\Core\Base +{ + public function createGroup($name, $external_id = '') + { + return $this->group->create($name, $external_id); + } + + public function updateGroup($group_id, $name = null, $external_id = null) + { + $values = array( + 'id' => $group_id, + 'name' => $name, + 'external_id' => $external_id, + ); + + foreach ($values as $key => $value) { + if (is_null($value)) { + unset($values[$key]); + } + } + + return $this->group->update($values); + } + + public function removeGroup($group_id) + { + return $this->group->remove($group_id); + } + + public function getGroup($group_id) + { + return $this->group->getById($group_id); + } + + public function getAllGroups() + { + return $this->group->getAll(); + } +} diff --git a/app/Api/GroupMember.php b/app/Api/GroupMember.php new file mode 100644 index 00000000..de62f0c6 --- /dev/null +++ b/app/Api/GroupMember.php @@ -0,0 +1,32 @@ +<?php + +namespace Kanboard\Api; + +/** + * Group Member API controller + * + * @package api + * @author Frederic Guillot + */ +class GroupMember extends \Kanboard\Core\Base +{ + public function getGroupMembers($group_id) + { + return $this->groupMember->getMembers($group_id); + } + + public function addGroupMember($group_id, $user_id) + { + return $this->groupMember->addUser($group_id, $user_id); + } + + public function removeGroupMember($group_id, $user_id) + { + return $this->groupMember->removeUser($group_id, $user_id); + } + + public function isGroupMember($group_id, $user_id) + { + return $this->groupMember->isMember($group_id, $user_id); + } +} diff --git a/app/Api/ProjectPermission.php b/app/Api/ProjectPermission.php index d4408197..11e92af0 100644 --- a/app/Api/ProjectPermission.php +++ b/app/Api/ProjectPermission.php @@ -5,25 +5,68 @@ namespace Kanboard\Api; use Kanboard\Core\Security\Role; /** - * ProjectPermission API controller + * Project Permission API controller * * @package api * @author Frederic Guillot */ class ProjectPermission extends \Kanboard\Core\Base { - public function getMembers($project_id) + public function getProjectUsers($project_id) { return $this->projectUserRole->getAllUsers($project_id); } - public function revokeUser($project_id, $user_id) + public function getAssignableUsers($project_id, $prepend_unassigned = false) + { + return $this->projectUserRole->getAssignableUsersList($project_id, $prepend_unassigned); + } + + public function addProjectUser($project_id, $user_id, $role = Role::PROJECT_MEMBER) + { + return $this->projectUserRole->addUser($project_id, $user_id, $role); + } + + public function addProjectGroup($project_id, $group_id, $role = Role::PROJECT_MEMBER) + { + return $this->projectGroupRole->addGroup($project_id, $group_id, $role); + } + + public function removeProjectUser($project_id, $user_id) { return $this->projectUserRole->removeUser($project_id, $user_id); } + public function removeProjectGroup($project_id, $group_id) + { + return $this->projectGroupRole->removeGroup($project_id, $group_id); + } + + public function changeProjectUserRole($project_id, $user_id, $role) + { + return $this->projectUserRole->changeUserRole($project_id, $user_id, $role); + } + + public function changeProjectGroupRole($project_id, $group_id, $role) + { + return $this->projectGroupRole->changeGroupRole($project_id, $group_id, $role); + } + + // Deprecated + public function getMembers($project_id) + { + return $this->getProjectUsers($project_id); + } + + // Deprecated + public function revokeUser($project_id, $user_id) + { + return $this->removeProjectUser($project_id, $user_id); + } + + // Deprecated public function allowUser($project_id, $user_id) { - return $this->projectUserRole->addUser($project_id, $user_id, Role::PROJECT_MEMBER); + return $this->addProjectUser($project_id, $user_id); } } diff --git a/app/Core/Security/Role.php b/app/Core/Security/Role.php index 85d85743..cb45a8af 100644 --- a/app/Core/Security/Role.php +++ b/app/Core/Security/Role.php @@ -50,7 +50,7 @@ class Role } /** - * Get application roles + * Get role name * * @access public * @param string $role diff --git a/doc/api-application-procedures.markdown b/doc/api-application-procedures.markdown index ab1230cc..2897b526 100644 --- a/doc/api-application-procedures.markdown +++ b/doc/api-application-procedures.markdown @@ -229,3 +229,63 @@ Response example: } } ``` + +### getApplicationRoles + +- Purpose: **Get the application roles** +- Parameters: none +- Result: **Dictionary of role => role_name** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getApplicationRoles", + "id": 317154243 +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 317154243, + "result": { + "app-admin": "Administrator", + "app-manager": "Manager", + "app-user": "User" + } +} +``` + +### getProjectRoles + +- Purpose: **Get the project roles** +- Parameters: none +- Result: **Dictionary of role => role_name** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getProjectRoles", + "id": 8981960 +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 8981960, + "result": { + "project-manager": "Project Manager", + "project-member": "Project Member", + "project-viewer": "Project Viewer" + } +} +```
\ No newline at end of file diff --git a/doc/api-group-member-procedures.markdown b/doc/api-group-member-procedures.markdown new file mode 100644 index 00000000..54153537 --- /dev/null +++ b/doc/api-group-member-procedures.markdown @@ -0,0 +1,152 @@ +Group Member API Procedures +=========================== + +### getGroupMembers + +- Purpose: **Get all members of a group** +- Parameters: + - **group_id** (integer, required) +- Result on success: **List of users** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getGroupMembers", + "id": 1987176726, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1987176726, + "result": [ + { + "group_id": "1", + "user_id": "1", + "id": "1", + "username": "admin", + "is_ldap_user": "0", + "name": null, + "email": null, + "notifications_enabled": "0", + "timezone": null, + "language": null, + "disable_login_form": "0", + "notifications_filter": "4", + "nb_failed_login": "0", + "lock_expiration_date": "0", + "is_project_admin": "0", + "gitlab_id": null, + "role": "app-admin" + } + ] +} +``` + +### addGroupMember + +- Purpose: **Add a user to a group** +- Parameters: + - **group_id** (integer, required) + - **user_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "addGroupMember", + "id": 1589058273, + "params": [ + 1, + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1589058273, + "result": true +} +``` + +### removeGroupMember + +- Purpose: **Remove a user from a group** +- Parameters: + - **group_id** (integer, required) + - **user_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeGroupMember", + "id": 1730416406, + "params": [ + 1, + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1730416406, + "result": true +} +``` + +### isGroupMember + +- Purpose: **Check if a user is member of a group** +- Parameters: + - **group_id** (integer, required) + - **user_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "isGroupMember", + "id": 1052800865, + "params": [ + 1, + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1052800865, + "result": false +} +``` diff --git a/doc/api-group-procedures.markdown b/doc/api-group-procedures.markdown new file mode 100644 index 00000000..1e976b8c --- /dev/null +++ b/doc/api-group-procedures.markdown @@ -0,0 +1,174 @@ +Group API Procedures +==================== + +### createGroup + +- Purpose: **Create a new group** +- Parameters: + - **name** (string, required) + - **external_id** (string, optional) +- Result on success: **link_id** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "createGroup", + "id": 1416806551, + "params": [ + "My Group B", + "1234" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1416806551, + "result": 2 +} +``` + +### updateGroup + +- Purpose: **Update a group** +- Parameters: + - **group_id** (integer, required) + - **name** (string, optional) + - **external_id** (string, optional) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "updateGroup", + "id": 866078030, + "params": { + "group_id": "1", + "name": "ABC", + "external_id": "something" + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 866078030, + "result": true +} +``` + +### removeGroup + +- Purpose: **Remove a group** +- Parameters: + - **group_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeGroup", + "id": 566000661, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 566000661, + "result": true +} +``` + +### getGroup + +- Purpose: **Get one group** +- Parameters: + - **group_id** (integer, required) +- Result on success: **Group dictionary** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getGroup", + "id": 1968647622, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1968647622, + "result": { + "id": "1", + "external_id": "", + "name": "My Group A" + } +} +``` + +### getAllGroups + +- Purpose: **Get all groups** +- Parameters: none +- Result on success: **list of groups** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getAllGroups", + "id": 546070742 +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 546070742, + "result": [ + { + "id": "1", + "external_id": "", + "name": "My Group A" + }, + { + "id": "2", + "external_id": "1234", + "name": "My Group B" + } + ] +} +``` diff --git a/doc/api-json-rpc.markdown b/doc/api-json-rpc.markdown index 710b0b1b..34559df5 100644 --- a/doc/api-json-rpc.markdown +++ b/doc/api-json-rpc.markdown @@ -48,16 +48,19 @@ Usage - [Authentication](api-authentication.markdown) - [Examples](api-examples.markdown) -- [Application Procedures](api-application-procedures.markdown) -- [Project Procedures](api-project-procedures.markdown) -- [Board Procedures](api-board-procedures.markdown) -- [Swimlane Procedures](api-swimlane-procedures.markdown) -- [Category Procedures](api-category-procedures.markdown) -- [Automatic Action Procedures](api-action-procedures.markdown) -- [Task Procedures](api-task-procedures.markdown) -- [Subtask Procedures](api-subtask-procedures.markdown) -- [File Procedures](api-file-procedures.markdown) -- [Link Procedures](api-link-procedures.markdown) -- [Comment Procedures](api-comment-procedures.markdown) -- [User Procedures](api-user-procedures.markdown) -- [User API Access Procedures](api-me-procedures.markdown) +- [Application](api-application-procedures.markdown) +- [Projects](api-project-procedures.markdown) +- [Project Permissions](api-project-permission-procedures.markdown) +- [Boards](api-board-procedures.markdown) +- [Swimlanes](api-swimlane-procedures.markdown) +- [Categories](api-category-procedures.markdown) +- [Automatic Actions](api-action-procedures.markdown) +- [Tasks](api-task-procedures.markdown) +- [Subtasks](api-subtask-procedures.markdown) +- [Files](api-file-procedures.markdown) +- [Links](api-link-procedures.markdown) +- [Comments](api-comment-procedures.markdown) +- [Users](api-user-procedures.markdown) +- [Groups](api-group-procedures.markdown) +- [Group Members](api-group-member-procedures.markdown) +- [Me](api-me-procedures.markdown) diff --git a/doc/api-project-permission-procedures.markdown b/doc/api-project-permission-procedures.markdown new file mode 100644 index 00000000..f64028b9 --- /dev/null +++ b/doc/api-project-permission-procedures.markdown @@ -0,0 +1,274 @@ +Project Permission API Procedures +================================= + +### getProjectUsers + +- Purpose: **Get all members of a project** +- Parameters: + - **project_id** (integer, required) +- Result on success: **Dictionary of user_id => user name** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getProjectUsers", + "id": 1601016721, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1601016721, + "result": { + "1": "admin" + } +} +``` + +### getAssignableUsers + +- Purpose: **Get users that can be assigned to a task for a project** (all members except viewers) +- Parameters: + - **project_id** (integer, required) + - **prepend_unassigned** (boolean, optional, default is false) +- Result on success: **Dictionary of user_id => user name** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "getAssignableUsers", + "id": 658294870, + "params": [ + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 658294870, + "result": { + "1": "admin" + } +} +``` + +### addProjectUser + +- Purpose: **Grant access to a project for a user** +- Parameters: + - **project_id** (integer, required) + - **user_id** (integer, required) + - **role** (string, optional) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "addProjectUser", + "id": 1294688355, + "params": [ + "1", + "1", + "project-viewer" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1294688355, + "result": true +} +``` + +### addProjectGroup + +- Purpose: **Grant access to a project for a group** +- Parameters: + - **project_id** (integer, required) + - **group_id** (integer, required) + - **role** (string, optional) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "addProjectGroup", + "id": 1694959089, + "params": [ + "1", + "1" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 1694959089, + "result": true +} +``` + +### removeProjectUser + +- Purpose: **Revoke user access to a project** +- Parameters: + - **project_id** (integer, required) + - **user_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeProjectUser", + "id": 645233805, + "params": [ + 1, + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 645233805, + "result": true +} +``` + +### removeProjectGroup + +- Purpose: **Revoke group access to a project** +- Parameters: + - **project_id** (integer, required) + - **group_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "removeProjectGroup", + "id": 557146966, + "params": [ + 1, + 1 + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 557146966, + "result": true +} +``` + +### changeProjectUserRole + +- Purpose: **Change role of a user for a project** +- Parameters: + - **project_id** (integer, required) + - **user_id** (integer, required) + - **role** (string, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "changeProjectUserRole", + "id": 193473170, + "params": [ + "1", + "1", + "project-viewer" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 193473170, + "result": true +} +``` + +### changeProjectGroupRole + +- Purpose: **Change role of a group for a project** +- Parameters: + - **project_id** (integer, required) + - **group_id** (integer, required) + - **role** (string, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "changeProjectGroupRole", + "id": 2114673298, + "params": [ + "1", + "1", + "project-viewer" + ] +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 2114673298, + "result": true +} +``` diff --git a/doc/api-project-procedures.markdown b/doc/api-project-procedures.markdown index acb4297e..2d23615b 100644 --- a/doc/api-project-procedures.markdown +++ b/doc/api-project-procedures.markdown @@ -409,104 +409,3 @@ Request example: ] } ``` - -### getMembers - -- Purpose: **Get members of a project** -- Parameters: - - **project_id** (integer, required) -- Result on success: Key/value pair of user_id and username -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "getMembers", - "id": 1944388643, - "params": [ - 1 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 1944388643, - "result": { - "1": "user1", - "2": "user2", - "3": "user3" - } -} -``` - -### revokeUser - -- Purpose: **Revoke user access for a given project** -- Parameters: - - **project_id** (integer, required) - - **user_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "revokeUser", - "id": 251218350, - "params": [ - 1, - 2 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 251218350, - "result": true -} -``` - -### allowUser - -- Purpose: **Grant user access for a given project** -- Parameters: - - **project_id** (integer, required) - - **user_id** (integer, required) -- Result on success: **true** -- Result on failure: **false** - -Request example: - -```json -{ - "jsonrpc": "2.0", - "method": "allowUser", - "id": 2111451404, - "params": [ - 1, - 2 - ] -} -``` - -Response example: - -```json -{ - "jsonrpc": "2.0", - "id": 2111451404, - "result": true -} -``` diff --git a/doc/tests.markdown b/doc/tests.markdown index 5e5ad389..b2d95491 100644 --- a/doc/tests.markdown +++ b/doc/tests.markdown @@ -1,7 +1,7 @@ -How to run units and functional tests? -====================================== +Automated tests +=============== -[PHPUnit](https://phpunit.de/) is used to run automatic tests on Kanboard. +[PHPUnit](https://phpunit.de/) is used to run automated tests on Kanboard. You can run tests across different databases (Sqlite, Mysql and Postgresql) to be sure that the result is the same everywhere. @@ -9,31 +9,18 @@ Requirements ------------ - Linux/Unix machine -- PHP command line +- PHP cli - PHPUnit installed - Mysql and Postgresql (optional) -Install the latest version of PHPUnit -------------------------------------- - -Simply download the PHPUnit PHAR et copy the file somewhere in your `$PATH`: - -```bash -wget https://phar.phpunit.de/phpunit.phar -chmod +x phpunit.phar -sudo mv phpunit.phar /usr/local/bin/phpunit -phpunit --version -PHPUnit 4.2.6 by Sebastian Bergmann. -``` - -Running unit tests ------------------- +Unit Tests +---------- -### Testing with Sqlite +### Test with Sqlite Sqlite tests use a in-memory database, nothing is written on the file system. -The config file is `tests/units.sqlite.xml`. +The PHPUnit config file is `tests/units.sqlite.xml`. From your Kanboard directory, run the command `phpunit -c tests/units.sqlite.xml`. Example: @@ -41,21 +28,26 @@ Example: ```bash phpunit -c tests/units.sqlite.xml -PHPUnit 4.2.6 by Sebastian Bergmann. +PHPUnit 5.0.0 by Sebastian Bergmann and contributors. -Configuration read from /Volumes/Devel/apps/kanboard/tests/units.sqlite.xml +............................................................... 63 / 649 ( 9%) +............................................................... 126 / 649 ( 19%) +............................................................... 189 / 649 ( 29%) +............................................................... 252 / 649 ( 38%) +............................................................... 315 / 649 ( 48%) +............................................................... 378 / 649 ( 58%) +............................................................... 441 / 649 ( 67%) +............................................................... 504 / 649 ( 77%) +............................................................... 567 / 649 ( 87%) +............................................................... 630 / 649 ( 97%) +................... 649 / 649 (100%) -................................................................. 65 / 74 ( 87%) -......... +Time: 1.22 minutes, Memory: 151.25Mb -Time: 9.05 seconds, Memory: 17.75Mb - -OK (74 tests, 6145 assertions) +OK (649 tests, 43595 assertions) ``` -**NOTE:** PHPUnit is already included in the Vagrant environment - -### Testing with Mysql +### Test with Mysql You must have Mysql or MariaDb installed on localhost. @@ -68,27 +60,10 @@ By default, those credentials are used: For each execution the database is dropped and created again. -The config file is `tests/units.mysql.xml`. +The PHPUnit config file is `tests/units.mysql.xml`. From your Kanboard directory, run the command `phpunit -c tests/units.mysql.xml`. -Example: - -```bash -phpunit -c tests/units.mysql.xml - -PHPUnit 4.2.6 by Sebastian Bergmann. - -Configuration read from /Volumes/Devel/apps/kanboard/tests/units.mysql.xml - -................................................................. 65 / 74 ( 87%) -......... - -Time: 49.77 seconds, Memory: 17.50Mb - -OK (74 tests, 6145 assertions) -``` - -### Testing with Postgresql +### Test with Postgresql You must have Postgresql installed on localhost. @@ -100,37 +75,20 @@ By default, those credentials are used: - Database: **kanboard_unit_test** Be sure to allow the user `postgres` to create and drop databases. -For each execution the database is dropped and created again. +The database is recreated for each execution. -The config file is `tests/units.postgres.xml`. +The PHPUnit config file is `tests/units.postgres.xml`. From your Kanboard directory, run the command `phpunit -c tests/units.postgres.xml`. -Example: - -```bash -phpunit -c tests/units.postgres.xml - -PHPUnit 4.2.6 by Sebastian Bergmann. - -Configuration read from /Volumes/Devel/apps/kanboard/tests/units.postgres.xml - -................................................................. 65 / 74 ( 87%) -......... - -Time: 52.66 seconds, Memory: 17.50Mb - -OK (74 tests, 6145 assertions) -``` - -Running functionals tests -------------------------- +Integration Tests +----------------- Actually only the API calls are tested. Real HTTP calls are made with those tests. -So a local instance of Kanboard is necessary and must listen on `http://localhost:8000`. +So a local instance of Kanboard is necessary and must listen on `http://localhost:8000/`. -Don't forget that all data will be removed/altered by the test suite. +All data will be removed/altered by the test suite. Moreover the script will reset and set a new API key. 1. Start a local instance of Kanboard `php -S 127.0.0.1:8000` @@ -138,27 +96,27 @@ Moreover the script will reset and set a new API key. The same method as above is used to run tests across different databases: -- Sqlite: `phpunit -c tests/functionals.sqlite.xml` -- Mysql: `phpunit -c tests/functionals.mysql.xml` -- Postgresql: `phpunit -c tests/functionals.postgres.xml` +- Sqlite: `phpunit -c tests/integration.sqlite.xml` +- Mysql: `phpunit -c tests/integration.mysql.xml` +- Postgresql: `phpunit -c tests/integration.postgres.xml` Example: ```bash -phpunit -c tests/functionals.sqlite.xml - -PHPUnit 4.2.6 by Sebastian Bergmann. +phpunit -c tests/integration.sqlite.xml -Configuration read from /Volumes/Devel/apps/kanboard/tests/functionals.sqlite.xml +PHPUnit 5.0.0 by Sebastian Bergmann and contributors. -.......................................... +............................................................... 63 / 135 ( 46%) +............................................................... 126 / 135 ( 93%) +......... 135 / 135 (100%) -Time: 1.72 seconds, Memory: 4.25Mb +Time: 1.18 minutes, Memory: 14.75Mb -OK (42 tests, 160 assertions) +OK (135 tests, 526 assertions) ``` -Continuous Integration with Travis-ci +Continuous Integration with Travis-CI ------------------------------------- After each commit pushed on the main repository, unit tests are executed across 5 different versions of PHP: @@ -171,6 +129,4 @@ After each commit pushed on the main repository, unit tests are executed across Each version of PHP is tested against the 3 supported database: Sqlite, Mysql and Postgresql. -That mean we run 15 jobs each time the repository is updated. The execution time is around 25 minutes. - The Travis config file `.travis.yml` is located on the root directory of Kanboard. diff --git a/jsonrpc.php b/jsonrpc.php index 6778603f..1d59d4ea 100644 --- a/jsonrpc.php +++ b/jsonrpc.php @@ -19,6 +19,8 @@ use Kanboard\Api\Swimlane; use Kanboard\Api\Task; use Kanboard\Api\TaskLink; use Kanboard\Api\User; +use Kanboard\Api\Group; +use Kanboard\Api\GroupMember; $server = new Server; $server->setAuthenticationHeader(API_AUTHENTICATION_HEADER); @@ -39,5 +41,7 @@ $server->attach(new Swimlane($container)); $server->attach(new Task($container)); $server->attach(new TaskLink($container)); $server->attach(new User($container)); +$server->attach(new Group($container)); +$server->attach(new GroupMember($container)); echo $server->execute(); diff --git a/tests/functionals.mysql.xml b/tests/integration.mysql.xml index a8877f92..30769371 100644 --- a/tests/functionals.mysql.xml +++ b/tests/integration.mysql.xml @@ -1,7 +1,7 @@ <phpunit stopOnError="true" stopOnFailure="true" colors="true"> <testsuites> <testsuite name="Kanboard"> - <directory>functionals</directory> + <directory>integration</directory> </testsuite> </testsuites> <php> diff --git a/tests/functionals.postgres.xml b/tests/integration.postgres.xml index 61da8574..ed8a3de3 100644 --- a/tests/functionals.postgres.xml +++ b/tests/integration.postgres.xml @@ -1,7 +1,7 @@ <phpunit stopOnError="true" stopOnFailure="true" colors="true"> <testsuites> <testsuite name="Kanboard"> - <directory>functionals</directory> + <directory>integration</directory> </testsuite> </testsuites> <php> diff --git a/tests/functionals.sqlite.xml b/tests/integration.sqlite.xml index 9da59b20..1964f822 100644 --- a/tests/functionals.sqlite.xml +++ b/tests/integration.sqlite.xml @@ -1,7 +1,7 @@ <phpunit stopOnError="true" stopOnFailure="true" colors="true"> <testsuites> <testsuite name="Kanboard"> - <directory>functionals</directory> + <directory>integration</directory> </testsuite> </testsuites> <php> diff --git a/tests/functionals/ApiTest.php b/tests/integration/ApiTest.php index b250254e..798bde42 100644 --- a/tests/functionals/ApiTest.php +++ b/tests/integration/ApiTest.php @@ -45,16 +45,6 @@ class Api extends PHPUnit_Framework_TestCase return $tasks[0]['id']; } - public function testGetTimezone() - { - $this->assertEquals('Europe/Paris', $this->client->getTimezone()); - } - - public function testGetVersion() - { - $this->assertEquals('master', $this->client->getVersion()); - } - public function testRemoveAll() { $projects = $this->client->getAllProjects(); diff --git a/tests/integration/AppTest.php b/tests/integration/AppTest.php new file mode 100644 index 00000000..6575fbb8 --- /dev/null +++ b/tests/integration/AppTest.php @@ -0,0 +1,34 @@ +<?php + +require_once __DIR__.'/Base.php'; + +class AppTest extends Base +{ + public function testGetTimezone() + { + $this->assertEquals('UTC', $this->app->getTimezone()); + } + + public function testGetVersion() + { + $this->assertEquals('master', $this->app->getVersion()); + } + + public function testGetApplicationRoles() + { + $roles = $this->app->getApplicationRoles(); + $this->assertCount(3, $roles); + $this->assertEquals('Administrator', $roles['app-admin']); + $this->assertEquals('Manager', $roles['app-manager']); + $this->assertEquals('User', $roles['app-user']); + } + + public function testGetProjectRoles() + { + $roles = $this->app->getProjectRoles(); + $this->assertCount(3, $roles); + $this->assertEquals('Project Manager', $roles['project-manager']); + $this->assertEquals('Project Member', $roles['project-member']); + $this->assertEquals('Project Viewer', $roles['project-viewer']); + } +} diff --git a/tests/integration/Base.php b/tests/integration/Base.php new file mode 100644 index 00000000..6facd9ce --- /dev/null +++ b/tests/integration/Base.php @@ -0,0 +1,62 @@ +<?php + +require_once __DIR__.'/../../vendor/autoload.php'; + +abstract class Base extends PHPUnit_Framework_TestCase +{ + protected $app = null; + protected $admin = null; + protected $user = null; + + public static function setUpBeforeClass() + { + if (DB_DRIVER === 'sqlite') { + @unlink(DB_FILENAME); + } elseif (DB_DRIVER === 'mysql') { + $pdo = new PDO('mysql:host='.DB_HOSTNAME, DB_USERNAME, DB_PASSWORD); + $pdo->exec('DROP DATABASE '.DB_NAME); + $pdo->exec('CREATE DATABASE '.DB_NAME); + $pdo = null; + } elseif (DB_DRIVER === 'postgres') { + $pdo = new PDO('pgsql:host='.DB_HOSTNAME, DB_USERNAME, DB_PASSWORD); + $pdo->exec('DROP DATABASE '.DB_NAME); + $pdo->exec('CREATE DATABASE '.DB_NAME.' WITH OWNER '.DB_USERNAME); + $pdo = null; + } + + $service = new Kanboard\ServiceProvider\DatabaseProvider; + + $db = $service->getInstance(); + $db->table('settings')->eq('option', 'api_token')->update(array('value' => API_KEY)); + $db->closeConnection(); + } + + public function setUp() + { + $this->app = new JsonRPC\Client(API_URL); + $this->app->authentication('jsonrpc', API_KEY); + $this->app->debug = true; + + $this->admin = new JsonRPC\Client(API_URL); + $this->admin->authentication('admin', 'admin'); + // $this->admin->debug = true; + + $this->user = new JsonRPC\Client(API_URL); + $this->user->authentication('user', 'password'); + // $this->user->debug = true; + } + + protected function getProjectId() + { + $projects = $this->app->getAllProjects(); + $this->assertNotEmpty($projects); + return $projects[0]['id']; + } + + protected function getGroupId() + { + $groups = $this->app->getAllGroups(); + $this->assertNotEmpty($groups); + return $groups[0]['id']; + } +} diff --git a/tests/integration/GroupMemberTest.php b/tests/integration/GroupMemberTest.php new file mode 100644 index 00000000..e84c0734 --- /dev/null +++ b/tests/integration/GroupMemberTest.php @@ -0,0 +1,39 @@ +<?php + +require_once __DIR__.'/Base.php'; + +class GroupMemberTest extends Base +{ + public function testAddMember() + { + $this->assertNotFalse($this->app->createGroup('My Group A')); + $this->assertNotFalse($this->app->createGroup('My Group B')); + + $groupId = $this->getGroupId(); + $this->assertTrue($this->app->addGroupMember($groupId, 1)); + } + + public function testGetMembers() + { + $groups = $this->app->getAllGroups(); + $members = $this->app->getGroupMembers($groups[0]['id']); + $this->assertCount(1, $members); + $this->assertEquals('admin', $members[0]['username']); + + $this->assertSame(array(), $this->app->getGroupMembers($groups[1]['id'])); + } + + public function testIsGroupMember() + { + $groupId = $this->getGroupId(); + $this->assertTrue($this->app->isGroupMember($groupId, 1)); + $this->assertFalse($this->app->isGroupMember($groupId, 2)); + } + + public function testRemove() + { + $groupId = $this->getGroupId(); + $this->assertTrue($this->app->removeGroupMember($groupId, 1)); + $this->assertFalse($this->app->isGroupMember($groupId, 1)); + } +} diff --git a/tests/integration/GroupTest.php b/tests/integration/GroupTest.php new file mode 100644 index 00000000..7a5bccc9 --- /dev/null +++ b/tests/integration/GroupTest.php @@ -0,0 +1,48 @@ +<?php + +require_once __DIR__.'/Base.php'; + +class GroupTest extends Base +{ + public function testCreateGroup() + { + $this->assertNotFalse($this->app->createGroup('My Group A')); + $this->assertNotFalse($this->app->createGroup('My Group B', '1234')); + } + + public function testGetter() + { + $groups = $this->app->getAllGroups(); + $this->assertCount(2, $groups); + $this->assertEquals('My Group A', $groups[0]['name']); + $this->assertEquals('', $groups[0]['external_id']); + $this->assertEquals('My Group B', $groups[1]['name']); + $this->assertEquals('1234', $groups[1]['external_id']); + + $group = $this->app->getGroup($groups[0]['id']); + $this->assertNotEmpty($group); + $this->assertEquals('My Group A', $group['name']); + $this->assertEquals('', $group['external_id']); + } + + public function testUpdate() + { + $groups = $this->app->getAllGroups(); + + $this->assertTrue($this->app->updateGroup(array('group_id' => $groups[0]['id'], 'name' => 'ABC', 'external_id' => 'something'))); + $this->assertTrue($this->app->updateGroup(array('group_id' => $groups[1]['id'], 'external_id' => ''))); + + $groups = $this->app->getAllGroups(); + $this->assertEquals('ABC', $groups[0]['name']); + $this->assertEquals('something', $groups[0]['external_id']); + $this->assertEquals('', $groups[1]['external_id']); + } + + public function testRemove() + { + $groups = $this->app->getAllGroups(); + $this->assertTrue($this->app->removeGroup($groups[0]['id'])); + $this->assertTrue($this->app->removeGroup($groups[1]['id'])); + $this->assertSame(array(), $this->app->getAllGroups()); + } +} diff --git a/tests/functionals/UserApiTest.php b/tests/integration/MeTest.php index 3c7fc04e..21f61756 100644 --- a/tests/functionals/UserApiTest.php +++ b/tests/integration/MeTest.php @@ -1,51 +1,9 @@ <?php -require_once __DIR__.'/../../vendor/autoload.php'; +require_once __DIR__.'/Base.php'; -class UserApi extends PHPUnit_Framework_TestCase +class MeTest extends Base { - private $app = null; - private $admin = null; - private $user = null; - - public static function setUpBeforeClass() - { - if (DB_DRIVER === 'sqlite') { - @unlink(DB_FILENAME); - } elseif (DB_DRIVER === 'mysql') { - $pdo = new PDO('mysql:host='.DB_HOSTNAME, DB_USERNAME, DB_PASSWORD); - $pdo->exec('DROP DATABASE '.DB_NAME); - $pdo->exec('CREATE DATABASE '.DB_NAME); - $pdo = null; - } elseif (DB_DRIVER === 'postgres') { - $pdo = new PDO('pgsql:host='.DB_HOSTNAME, DB_USERNAME, DB_PASSWORD); - $pdo->exec('DROP DATABASE '.DB_NAME); - $pdo->exec('CREATE DATABASE '.DB_NAME.' WITH OWNER '.DB_USERNAME); - $pdo = null; - } - - $service = new Kanboard\ServiceProvider\DatabaseProvider; - - $db = $service->getInstance(); - $db->table('settings')->eq('option', 'api_token')->update(array('value' => API_KEY)); - $db->closeConnection(); - } - - public function setUp() - { - $this->app = new JsonRPC\Client(API_URL); - $this->app->authentication('jsonrpc', API_KEY); - // $this->app->debug = true; - - $this->admin = new JsonRPC\Client(API_URL); - $this->admin->authentication('admin', 'admin'); - // $this->admin->debug = true; - - $this->user = new JsonRPC\Client(API_URL); - $this->user->authentication('user', 'password'); - // $this->user->debug = true; - } - public function testCreateProject() { $this->assertEquals(1, $this->app->createProject('team project')); diff --git a/tests/integration/ProjectPermissionTest.php b/tests/integration/ProjectPermissionTest.php new file mode 100644 index 00000000..b06ad4ad --- /dev/null +++ b/tests/integration/ProjectPermissionTest.php @@ -0,0 +1,64 @@ +<?php + +require_once __DIR__.'/Base.php'; + +class ProjectPermissionTest extends Base +{ + public function testGetProjectUsers() + { + $this->assertNotFalse($this->app->createProject('Test')); + $this->assertNotFalse($this->app->createGroup('Test')); + + $projectId = $this->getProjectId(); + $groupId = $this->getGroupId(); + + $this->assertTrue($this->app->addGroupMember($projectId, $groupId)); + $this->assertSame(array(), $this->app->getProjectUsers($projectId)); + } + + public function testProjectUser() + { + $projectId = $this->getProjectId(); + $this->assertTrue($this->app->addProjectUser($projectId, 1)); + + $users = $this->app->getProjectUsers($projectId); + $this->assertCount(1, $users); + $this->assertEquals('admin', $users[1]); + + $users = $this->app->getAssignableUsers($projectId); + $this->assertCount(1, $users); + $this->assertEquals('admin', $users[1]); + + $this->assertTrue($this->app->changeProjectUserRole($projectId, 1, 'project-viewer')); + + $users = $this->app->getAssignableUsers($projectId); + $this->assertCount(0, $users); + + $this->assertTrue($this->app->removeProjectUser($projectId, 1)); + $this->assertSame(array(), $this->app->getProjectUsers($projectId)); + } + + public function testProjectGroup() + { + $projectId = $this->getProjectId(); + $groupId = $this->getGroupId(); + + $this->assertTrue($this->app->addProjectGroup($projectId, $groupId)); + + $users = $this->app->getProjectUsers($projectId); + $this->assertCount(1, $users); + $this->assertEquals('admin', $users[1]); + + $users = $this->app->getAssignableUsers($projectId); + $this->assertCount(1, $users); + $this->assertEquals('admin', $users[1]); + + $this->assertTrue($this->app->changeProjectGroupRole($projectId, $groupId, 'project-viewer')); + + $users = $this->app->getAssignableUsers($projectId); + $this->assertCount(0, $users); + + $this->assertTrue($this->app->removeProjectGroup($projectId, 1)); + $this->assertSame(array(), $this->app->getProjectUsers($projectId)); + } +} |