diff --git a/.gitignore b/.gitignore index 45b11dbb..780d7f59 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ nbproject # Mac OS .DS_Store +# test files +clover.xml + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..7a6b48bf --- /dev/null +++ b/.travis.yml @@ -0,0 +1,59 @@ +language: php +php: + - 5.6 + - 7.0 + - 7.1 + +env: + global: + - CORE_BRANCH=master + - APP_NAME=customgroups + matrix: + - DB=sqlite + +branches: + only: + - master + +before_install: + - wget https://raw.githubusercontent.com/owncloud/administration/master/travis-ci/before_install.sh + - bash ./before_install.sh $APP_NAME $CORE_BRANCH $DB + + # Add some output debugging information + - cd ../core + - ./occ check + - ./occ status + - ./occ app:list + +script: + - cd apps/$APP_NAME/ + + # Test the app + - sh -c "if [ '$JSTESTS' != '1' -a '$CODECHECK' = '1' ]; then make test-codecheck; fi" + - sh -c "if [ '$JSTESTS' != '1' -a '$CODECHECK' = '1' ]; then make test-codecheck; fi" + - sh -c "if [ '$JSTESTS' != '1' -a '$CODECHECK' = '2' ]; then make test-codecheck-deprecations; fi" + + # Run phpunit tests + - sh -c "if [ '$JSTESTS' != '1' -a '$CODECHECK' != '1' -a '$CODECHECK' != '2' ]; then make test-php; fi" + + # Create and upload coverage report + - sh -c "if [ '$JSTESTS' != '1' -a '$CODECHECK' != '1' -a '$CODECHECK' != '2' ]; then make test-upload-coverage; fi" + + # Run Javascript unit tests + - make test-js + +matrix: + include: + - php: 5.6 + env: DB=mysql + - php: 5.6 + env: DB=pgsql + - php: 5.6 + env: DB=mysql;CODECHECK=1 + - php: 5.6 + env: DB=mysql;CODECHECK=2 + - php: 5.6 + env: DB=mysql;JSTESTS=1 + allow_failures: + - env: DB=mysql;CODECHECK=2 + fast_finish: true diff --git a/Makefile b/Makefile index 325c1b4f..3b8a14ce 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,27 @@ -# Makefile for building the project +# Makefile + +OWNCLOUD_PATH=$(CURDIR)/../.. +OCC=$(OWNCLOUD_PATH)/occ +PHPUNIT=$(OWNCLOUD_PATH)/lib/composer/phpunit/phpunit/phpunit app_name=customgroups project_dir=$(CURDIR)/../$(app_name) build_dir=$(CURDIR)/build doc_files=README.md src_files= -src_dirs=appinfo controller css img js l10n lib templates +src_dirs=appinfo lib all_src=$(src_files) $(src_dirs) $(doc_files) market_dir=$(build_dir)/market .PHONY: all -all: dist market +all: market +.PHONE: market market: dist cd $(build_dir); tar cvzf $(app_name).tar.gz $(app_name) rm -Rf $(market_dir); mkdir -p $(market_dir) mv $(build_dir)/$(app_name).tar.gz $(market_dir) - $(build_dir)/$(app_name): rm -Rf $@; mkdir -p $@ cp -R $(all_src) $@ @@ -29,6 +33,42 @@ dist: $(build_dir)/$(app_name) distclean: clean .PHONY: clean -clean: +clean: clean-test rm -rf $(build_dir) +.PHONY: clean-test +clean-test: + rm tests/unit/clover.xml + rm tests/unit/*.phar + +.PHONY: test-syntax +test-syntax: + for F in $(shell find . -name \*.php); do \ + php -l "$$F" || exit $?; \ + done + +.PHONY: test-codecheck +test-codecheck: test-syntax + $(OCC) app:check-code $(app_name) -c private -c strong-comparison + +.PHONY: test-codecheck-deprecations +test-codecheck-deprecations: + $(OCC) app:check-code $(app_name) -c deprecation + +.PHONY: test-php +test-php: test-syntax + $(OCC) app:enable $(app_name) + cd tests/unit && $(PHPUNIT) --configuration phpunit.xml + +.PHONY: test-upload-coverage +test-upload-coverage: + cd tests/unit && wget https://scrutinizer-ci.com/ocular.phar; php ocular.phar code-coverage:upload --format=php-clover clover.xml + +.PHONY: test-js +test-js: + @echo No JS unit tests currently + #cd tests/js && npm install --deps; node_modules/karma/bin/karma start karma.config.js --single-run; + +.PHONY: test +test: test-codecheck test-codecheck-deprecations test-php test-js + diff --git a/appinfo/Migrations/Version20161209151129.php b/appinfo/Migrations/Version20161209151129.php index 989951c1..58393699 100644 --- a/appinfo/Migrations/Version20161209151129.php +++ b/appinfo/Migrations/Version20161209151129.php @@ -23,6 +23,7 @@ private function createGroupsTable(Schema $schema) { $table->addColumn('group_id', 'integer', [ 'autoincrement' => true, 'unsigned' => true, + 'notnull' => true, 'length' => 4, ]); $table->addColumn('uri', 'string', [ @@ -42,8 +43,8 @@ private function createMembersTable(Schema $schema) { $prefix = $this->connection->getPrefix(); $table = $schema->createTable("${prefix}custom_group_member"); $table->addColumn('group_id', 'integer', [ - 'autoincrement' => true, 'unsigned' => true, + 'notnull' => true, 'length' => 4, ]); $table->addColumn('user_id', 'string', [ diff --git a/appinfo/app.php b/appinfo/app.php index 58d1db86..9ccd6640 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -19,3 +19,5 @@ * */ +$app = new \OCA\CustomGroups\Application(); +$app->registerGroupBackend(); diff --git a/appinfo/info.xml b/appinfo/info.xml index 99941f68..e0f7e00b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -2,6 +2,7 @@ customgroups User-defined custom groups + CustomGroups Let users create and manage custom groups for sharing. https://github.com/owncloud/customgroups/ https://github.com/owncloud/customgroups/issues @@ -11,6 +12,7 @@ 0.1 + true diff --git a/lib/Application.php b/lib/Application.php new file mode 100644 index 00000000..493af54d --- /dev/null +++ b/lib/Application.php @@ -0,0 +1,38 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\CustomGroups; + +use OCP\AppFramework\App; + +class Application extends App { + public function __construct (array $urlParams = array()) { + parent::__construct('customgroups', $urlParams); + } + + /** + * Register the group manager + */ + public function registerGroupBackend() { + $backend = $this->getContainer()->query('\OCA\CustomGroups\CustomGroupsBackend'); + $this->getContainer()->getServer()->getGroupManager()->addBackend($backend); + } +} diff --git a/lib/CustomGroupsBackend.php b/lib/CustomGroupsBackend.php new file mode 100644 index 00000000..7275cdab --- /dev/null +++ b/lib/CustomGroupsBackend.php @@ -0,0 +1,176 @@ + + * + * @copyright Copyright (c) 2016, ownCloud GmbH. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\CustomGroups; + +/** + * Group backend for custom groups for integration with core + */ +class CustomGroupsBackend implements \OCP\GroupInterface { + + const GROUP_ID_PREFIX = 'customgroup_'; + + /** + * @var CustomGroupsDatabaseHandler + */ + private $handler; + + public function __construct( + CustomGroupsDatabaseHandler $handler + ) { + $this->handler = $handler; + } + + /** + * Checks if backend implements actions. + * + * @param int $actions bitwise-or'ed actions + * @return boolean + */ + public function implementsActions($actions) { + return ($actions & self::GROUP_DETAILS) !== 0; + } + + /** + * Checks whether the user is member of a group or not. + * + * @param string $uid uid of the user + * @param string $gid gid of the group + * @return bool + */ + public function inGroup($uid, $gid) { + $numericGroupId = $this->extractNumericGroupId($gid); + if (is_null($numericGroupId)) { + return false; + } + + return $this->handler->inGroup($uid, $numericGroupId); + } + + /** + * Get all groups a user belongs to + * + * @param string $uid Name of the user + * @return array an array of group names + */ + public function getUserGroups($uid) { + $groups = $this->handler->getUserGroups($uid); + return array_map(function($numericGroupId) { + return $this->formatGroupId($numericGroupId); + }, $groups); + } + + /** + * Returns a list with all groups. + * The search string will match any part of the display name + * field. + * + * @param string $search search string + * @param int $limit limit or -1 to disable + * @param int $offset offset + * @return array an array of group names + * + */ + public function getGroups($search = '', $limit = -1, $offset = 0) { + $groups = $this->handler->searchGroups($search, $limit, $offset); + return array_map(function($groupInfo) { + return $this->formatGroupId($groupInfo['group_id']); + }, $groups); + } + + /** + * Checks if a group exists + * + * @param string $gid group id + * @return bool true if the group exists, false otherwise + */ + public function groupExists($gid) { + return !is_null($this->getGroupDetails($gid)); + } + + /** + * Returns the info for a given group. + * + * @param string $gid group id + * @return array|null group info or null if not found + */ + public function getGroupDetails($gid) { + $numericGroupId = $this->extractNumericGroupId($gid); + if (is_null($numericGroupId)) { + return null; + } + + $group = $this->handler->getGroup($numericGroupId); + if (is_null($group)) { + return null; + } + return [ + 'gid' => $this->formatGroupId($group['group_id']), + 'displayName' => $group['display_name'], + ]; + } + + /** + * Returns a list of all users in a group + * + * @param string $gid + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of user ids + */ + public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) { + // not exposed to regular user management + return []; + } + + /** + * Extracts the numeric id from the group id string with + * the format "customgroup_$id" + * + * @param string $gid group id in format "customgroup_$id" + * @return extracted numeric id or null if the format did not match + */ + private function extractNumericGroupId($gid) { + $len = strlen(self::GROUP_ID_PREFIX); + $prefixPart = substr($gid, 0, $len); + if ($prefixPart !== self::GROUP_ID_PREFIX) { + return null; + } + + $numericPart = substr($gid, $len); + + if (!is_numeric($numericPart)) { + return null; + } + return (int)$numericPart; + } + + /** + * Formats the given numeric group id to a string + * + * @param int $numericId numeric group id + * @return formatted id in format "customgroup_$id" + */ + private function formatGroupId($numericId) { + return self::GROUP_ID_PREFIX . $numericId; + } +} diff --git a/lib/CustomGroupsDatabaseHandler.php b/lib/CustomGroupsDatabaseHandler.php new file mode 100644 index 00000000..f237b314 --- /dev/null +++ b/lib/CustomGroupsDatabaseHandler.php @@ -0,0 +1,356 @@ + + * + * @copyright Copyright (c) 2016, ownCloud GmbH. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\CustomGroups; + +use OCP\IDBConnection; +use OCP\ILogger; + +/** + * Database handler for custom groups + */ +class CustomGroupsDatabaseHandler { + + /** + * @var IDBConnection + */ + private $dbConn; + + /** + * @var ILogger + */ + private $logger; + + + public function __construct(IDBConnection $dbConn, ILogger $logger) { + $this->dbConn = $dbConn; + $this->logger = $logger; + } + + /** + * Checks whether the user is member of a group or not. + * + * @param string $uid uid of the user + * @param int $numericGroupId id of the group + * @return bool + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function inGroup($uid, $numericGroupId) { + $qb = $this->dbConn->getQueryBuilder(); + + $cursor = $qb->select('user_id') + ->from('custom_group_member') + ->where($qb->expr()->eq('group_id', $qb->createNamedParameter($numericGroupId))) + ->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($uid))) + ->execute(); + + $result = $cursor->fetch(); + $cursor->closeCursor(); + + return $result ? true : false; + } + + /** + * Get all groups a user belongs to + * + * @param string $uid Name of the user + * @return array an array of numeric group ids + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function getUserGroups($uid) { + $qb = $this->dbConn->getQueryBuilder(); + $cursor = $qb->select('group_id') + ->from('custom_group_member') + ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($uid))) + ->orderBy('group_id', 'ASC') + ->execute(); + + $groups = []; + while ($row = $cursor->fetch()) { + $groups[] = (int)$row['group_id']; + } + $cursor->closeCursor(); + + return $groups; + } + + /** + * Searches groups by display names where the given + * search string can appear anywhere within the display name + * field. + * + * @param string $search search string + * @param int $limit limit or -1 to disable + * @param int $offset offset + * @return array an array of group info + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function searchGroups($search = '', $limit = -1, $offset = 0) { + $qb = $this->dbConn->getQueryBuilder(); + $qb->select(['group_id', 'uri', 'display_name']) + ->from('custom_group') + ->orderBy('display_name', 'ASC'); + + if ($search !== '') { + $likeString = '%' . $this->dbConn->escapeLikeParameter(strtolower($search)) . '%'; + $qb->where($qb->expr()->like($qb->createFunction('LOWER(`display_name`)'), $qb->createNamedParameter($likeString))); + } + + if ($limit > 0) { + $qb->setMaxResults($limit); + } + + if ($offset !== 0) { + $qb->setFirstResult($offset); + } + + $cursor = $qb->execute(); + $groups = $cursor->fetchAll(); + $cursor->closeCursor(); + return $groups; + } + + /** + * Returns the info for a given group. + * + * @param string $numericGroupId group id + * @return array|null group info or null if not found + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function getGroup($numericGroupId) { + return $this->getGroupBy('group_id', $numericGroupId); + } + + /** + * Returns the info for a given group. + * + * @param string $uri group uri + * @return array|null group info or null if not found + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function getGroupByUri($uri) { + return $this->getGroupBy('uri', $uri); + } + + /** + * Returns the info for a given group. + * + * @param string $gid group id + * @return array|null group info or null if not found + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + private function getGroupBy($field, $numericGroupId) { + $qb = $this->dbConn->getQueryBuilder(); + $cursor = $qb->select(['group_id', 'uri', 'display_name']) + ->from('custom_group') + ->where($qb->expr()->eq($field, $qb->createNamedParameter($numericGroupId))) + ->execute(); + $result = $cursor->fetch(); + $cursor->closeCursor(); + + if (!$result) { + return null; + } + + return $result; + } + + /** + * Returns the info for all groups. + * + * @return array array of group info + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function getGroups() { + return $this->searchGroups(); + } + + /** + * Creates a new group + * + * @param string $displayName display name + * @return int group id + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function createGroup($uri, $displayName = null) { + try { + $result = $this->dbConn->insertIfNotExist('*PREFIX*custom_group', [ + 'uri' => $uri, + 'display_name' => $displayName, + ]); + } catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) { + $this->logger->logException($e, ['app' => 'customgroups', 'message' => 'Cannot create a group that already exists']); + return null; + } + + if ($result === 1) { + return $this->dbConn->lastInsertId('*PREFIX*custom_group'); + } + + return null; + } + + /** + * Deletes the group with the given id + * + * @param int $gid numeric group id + * @return true if group was deleted, false otherwise + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function deleteGroup($gid) { + $this->dbConn->beginTransaction(); + // Delete the group-user relation + $qb = $this->dbConn->getQueryBuilder(); + $qb->delete('custom_group_member') + ->where($qb->expr()->eq('group_id', $qb->createNamedParameter($gid))) + ->execute(); + + // Delete the group + $qb = $this->dbConn->getQueryBuilder(); + $result = $qb->delete('custom_group') + ->where($qb->expr()->eq('group_id', $qb->createNamedParameter($gid))) + ->execute(); + $this->dbConn->commit(); + + return ($result === 1); + } + + /** + * Add a user to a group. + * + * @param string $uid user id + * @param int $gid numeric group id + * @return bool true if user was added, false otherwise + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function addToGroup($uid, $gid, $isAdmin = false) { + $result = $this->dbConn->insertIfNotExist('*PREFIX*custom_group_member', [ + 'user_id' => $uid, + 'group_id' => $gid, + 'is_admin' => $isAdmin ? 1 : 0 + ]); + + return ($result === 1); + } + + /** + * Remove a user from a group. + * + * @param string $uid user id + * @param int $gid numeric group id + * @return bool true if user was removed, false otherwise + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function removeFromGroup($uid, $gid) { + $qb = $this->dbConn->getQueryBuilder(); + $result = $qb->delete('custom_group_member') + ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($uid))) + ->andWhere($qb->expr()->eq('group_id', $qb->createNamedParameter($gid))) + ->execute(); + + return ($result === 1); + } + + /** + * Returns the group members + * + * @param int $gid numeric group id + * @return array array of member info + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function getGroupMembers($gid) { + $qb = $this->dbConn->getQueryBuilder(); + $cursor = $qb->select(['user_id', 'group_id', 'is_admin']) + ->from('custom_group_member') + ->where($qb->expr()->eq('group_id', $qb->createNamedParameter($gid))) + ->orderBy('user_id', 'ASC') + ->execute(); + + $results = []; + while ($row = $cursor->fetch()) { + $results[] = $this->formatMemberInfo($row); + } + $cursor->closeCursor(); + + return $results; + } + + /** + * Returns a specific group member info + * + * @param int $gid numeric group id + * @param int $uid user id + * @return array member info + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function getGroupMemberInfo($gid, $uid) { + $qb = $this->dbConn->getQueryBuilder(); + $cursor = $qb->select(['user_id', 'group_id', 'is_admin']) + ->from('custom_group_member') + ->where($qb->expr()->eq('group_id', $qb->createNamedParameter($gid))) + ->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($uid))) + ->orderBy('user_id', 'ASC') + ->execute(); + + $result = $cursor->fetchAll(); + $cursor->closeCursor(); + + if (empty($result)) { + return null; + } + + return $this->formatMemberInfo($result[0]); + } + + /** + * Update group member info + * + * @param int $gid numeric group id + * @param int $uid user id + * @param bool $isAdmin whether the member is a group admin + * @return bool true if the info got updated, false otherwise + * @throws \Doctrine\DBAL\Exception\DriverException in case of database exception + */ + public function setGroupMemberInfo($gid, $uid, $isAdmin) { + $qb = $this->dbConn->getQueryBuilder(); + $result = $qb->update('custom_group_member') + ->set('is_admin', $qb->createNamedParameter($isAdmin ? 1 : 0)) + ->where($qb->expr()->eq('group_id', $qb->createNamedParameter($gid))) + ->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($uid))) + ->execute(); + + return $result === 1; + } + + /** + * Formats the group member info from the database + * + * @param array $row database row + * @return array formatted array + */ + private function formatMemberInfo($row) { + return [ + 'user_id' => $row['user_id'], + 'group_id' => $row['group_id'], + 'is_admin' => (int)$row['is_admin'] !== 0, + ]; + } +} diff --git a/tests/unit/CustomGroupsBackendTest.php b/tests/unit/CustomGroupsBackendTest.php new file mode 100644 index 00000000..5fcd269d --- /dev/null +++ b/tests/unit/CustomGroupsBackendTest.php @@ -0,0 +1,146 @@ + + * + * @copyright Copyright (c) 2016, ownCloud GmbH. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OCA\CustomGroups\Tests\unit; + +use OCP\IDBConnection; +use OCA\CustomGroups\CustomGroupsDatabaseHandler; +use OCA\CustomGroups\CustomGroupsBackend; +use OCP\GroupInterface; + +/** + * Class CustomGroupsBackendTest + * + * @package OCA\CustomGroups\Tests\Unit + */ +class CustomGroupsBackendTest extends \Test\TestCase { + const GROUP_ID_PREFIX = CustomGroupsBackend::GROUP_ID_PREFIX; + + /** + * @var CustomGroupsDatabaseHandler + */ + private $handler; + + public function setUp() { + parent::setUp(); + $this->handler = $this->createMock(CustomGroupsDatabaseHandler::class); + $this->backend = new CustomGroupsBackend($this->handler); + } + + public function testImplementsAction() { + $this->assertTrue($this->backend->implementsActions(GroupInterface::GROUP_DETAILS)); + $this->assertFalse($this->backend->implementsActions(GroupInterface::CREATE_GROUP)); + $this->assertFalse($this->backend->implementsActions(GroupInterface::DELETE_GROUP)); + $this->assertFalse($this->backend->implementsActions(GroupInterface::ADD_TO_GROUP)); + $this->assertFalse($this->backend->implementsActions(GroupInterface::REMOVE_FROM_GROUP)); + $this->assertFalse($this->backend->implementsActions(GroupInterface::COUNT_USERS)); + } + + public function testInGroup() { + $this->handler->expects($this->any()) + ->method('inGroup') + ->will($this->returnValueMap([ + ['user1', 1, true], + ['user2', 1, false], + ])); + + $this->assertTrue($this->backend->inGroup('user1', self::GROUP_ID_PREFIX . '1')); + $this->assertFalse($this->backend->inGroup('user2', self::GROUP_ID_PREFIX . '1')); + $this->assertFalse($this->backend->inGroup('user1', '1')); + } + + public function testGetUserGroups() { + $this->handler->expects($this->any()) + ->method('getUserGroups') + ->will($this->returnValueMap([ + ['user1', [1, 2]], + ['user2', [1, 3]], + ])); + + $this->assertEquals( + [ + self::GROUP_ID_PREFIX . '1', + self::GROUP_ID_PREFIX . '2', + ], + $this->backend->getUserGroups('user1') + ); + $this->assertEquals( + [ + self::GROUP_ID_PREFIX . '1', + self::GROUP_ID_PREFIX . '3', + ], + $this->backend->getUserGroups('user2') + ); + } + + public function testGetGroups() { + $this->handler->expects($this->any()) + ->method('searchGroups') + ->with('ser', 10, 5) + ->will($this->returnValue([ + ['group_id' => 1], + ['group_id' => 2], + ])); + + $this->assertEquals( + [ + self::GROUP_ID_PREFIX . '1', + self::GROUP_ID_PREFIX . '2', + ], + $this->backend->getGroups('ser', 10, 5) + ); + } + + public function testGroupExists() { + $this->handler->expects($this->any()) + ->method('getGroup') + ->will($this->returnValueMap([ + [1, ['group_id' => 1, 'display_name' => 'Group One']], + [2, null], + ])); + + $this->assertTrue($this->backend->groupExists(self::GROUP_ID_PREFIX . '1')); + $this->assertFalse($this->backend->groupExists(self::GROUP_ID_PREFIX . '2')); + $this->assertFalse($this->backend->groupExists(1)); + } + + public function testGetGroupDetails() { + $this->handler->expects($this->any()) + ->method('getGroup') + ->will($this->returnValueMap([ + [1, ['group_id' => 1, 'display_name' => 'Group One']], + [2, null], + ])); + + $groupInfo = $this->backend->getGroupDetails(self::GROUP_ID_PREFIX . '1'); + $this->assertEquals(self::GROUP_ID_PREFIX . '1', $groupInfo['gid']); + $this->assertEquals('Group One', $groupInfo['displayName']); + + $this->assertNull($this->backend->getGroupDetails(self::GROUP_ID_PREFIX . '2')); + $this->assertNull($this->backend->getGroupDetails(1)); + } + + public function testUsersInGroup() { + $this->handler->expects($this->never())->method('getGroup'); + $this->handler->expects($this->never())->method('getGroupMembers'); + $this->assertEquals([], $this->backend->usersInGroup(self::GROUP_ID_PREFIX . '1')); + } + +} diff --git a/tests/unit/CustomGroupsDatabaseHandlerTest.php b/tests/unit/CustomGroupsDatabaseHandlerTest.php new file mode 100644 index 00000000..d78e2a2a --- /dev/null +++ b/tests/unit/CustomGroupsDatabaseHandlerTest.php @@ -0,0 +1,290 @@ + + * + * @copyright Copyright (c) 2016, ownCloud GmbH. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OCA\CustomGroups\Tests\unit; + +use OCP\IDBConnection; +use OCA\CustomGroups\CustomGroupsDatabaseHandler; +use OCP\ILogger; + +/** + * Class CustomGroupsDatabaseHandlerTest + * + * @group DB + * + * @package OCA\CustomGroups\Tests\Unit + */ +class CustomGroupsDatabaseHandlerTest extends \Test\TestCase { + + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var CustomGroupsDatabaseHandler + */ + private $handler; + + /** + * @var ILogger + */ + private $logger; + + public function setUp() { + parent::setUp(); + $this->connection = \OC::$server->getDatabaseConnection(); + $this->logger = $this->createMock(ILogger::class); + $this->handler = new CustomGroupsDatabaseHandler($this->connection, $this->logger); + } + + public function tearDown() { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('custom_group_member')->execute(); + $qb->delete('custom_group')->execute(); + } + + public function testCreateGroup() { + $this->assertNotNull($this->handler->createGroup('my_group', 'My Group')); + // recreating returns null + $this->assertNull($this->handler->createGroup('my_group', 'My Group')); + } + + public function testCreateGroupDuplicateUri() { + $this->assertNotNull($this->handler->createGroup('my_group', 'My Group')); + $this->logger->expects($this->once())->method('logException'); + $this->assertNull($this->handler->createGroup('my_group', 'Different display name')); + } + + public function testDeleteGroup() { + $groupId = $this->handler->createGroup('my_group', 'My Group'); + $this->assertTrue($this->handler->deleteGroup($groupId)); + + $this->assertNull($this->handler->getGroup($groupId)); + + $this->assertFalse($this->handler->deleteGroup($groupId)); + } + + public function testSearchGroups() { + $group1Id = $this->handler->createGroup('my_group_1', 'My One Group'); + $group2Id = $this->handler->createGroup('my_group_2', 'My Group Two'); + $group3Id = $this->handler->createGroup('my_group_3', 'AA One'); + + $results = $this->handler->searchGroups('one'); + $this->assertCount(2, $results); + + // results sorted by display_name + $this->assertEquals('my_group_3', $results[0]['uri']); + $this->assertEquals('AA One', $results[0]['display_name']); + $this->assertEquals($group3Id, $results[0]['group_id']); + + $this->assertEquals('my_group_1', $results[1]['uri']); + $this->assertEquals('My One Group', $results[1]['display_name']); + $this->assertEquals($group1Id, $results[1]['group_id']); + } + + public function testSearchGroupsPagination() { + $count = 30; + for ($i = 0; $i < $count; $i++) { + $num = (string)$i; + if (strlen($num) === 1) { + // doing this for a correct display_name sort because + // usually 1 comes before 10 in DB sorts... + $num = '0' . $num; + } + $groupIds[$i] = $this->handler->createGroup('my_group_' . $num, 'My Group ' . $num); + } + + $results = $this->handler->searchGroups('Group', 3, 5); + $this->assertCount(3, $results); + + $this->assertEquals($groupIds[5], $results[0]['group_id']); + $this->assertEquals($groupIds[6], $results[1]['group_id']); + $this->assertEquals($groupIds[7], $results[2]['group_id']); + + // search beyond last page + $results = $this->handler->searchGroups('Group', 100, 5); + $this->assertCount(25, $results); + } + + public function testGetGroup() { + $groupId = $this->handler->createGroup('my_group', 'My Group'); + $this->assertNotNull($groupId); + + $groupInfo = $this->handler->getGroup($groupId); + $this->assertEquals($groupId, $groupInfo['group_id']); + $this->assertEquals('My Group', $groupInfo['display_name']); + $this->assertEquals('my_group', $groupInfo['uri']); + + $this->assertNull($this->handler->getGroup(-100)); + } + + public function testGetGroupByUri() { + $groupId = $this->handler->createGroup('my_group', 'My Group'); + $this->assertNotNull($groupId); + + $groupInfo = $this->handler->getGroupByUri('my_group'); + $this->assertEquals($groupId, $groupInfo['group_id']); + $this->assertEquals('My Group', $groupInfo['display_name']); + $this->assertEquals('my_group', $groupInfo['uri']); + + $this->assertNull($this->handler->getGroupByUri('unexist')); + } + + public function testGetGroups() { + $group1Id = $this->handler->createGroup('my_group_1', 'My One Group'); + $group2Id = $this->handler->createGroup('my_group_2', 'My Group Two'); + $group3Id = $this->handler->createGroup('my_group_3', 'AA One'); + + $results = $this->handler->getGroups(); + + $this->assertCount(3, $results); + + // results sorted by display_name + $this->assertEquals('my_group_3', $results[0]['uri']); + $this->assertEquals('AA One', $results[0]['display_name']); + $this->assertEquals($group3Id, $results[0]['group_id']); + + $this->assertEquals('my_group_2', $results[1]['uri']); + $this->assertEquals('My Group Two', $results[1]['display_name']); + $this->assertEquals($group2Id, $results[1]['group_id']); + + $this->assertEquals('my_group_1', $results[2]['uri']); + $this->assertEquals('My One Group', $results[2]['display_name']); + $this->assertEquals($group1Id, $results[2]['group_id']); + } + + public function testAddToGroup() { + $groupId = $this->handler->createGroup('my_group', 'My Group'); + + $this->assertTrue($this->handler->addToGroup('user2', $groupId, false)); + $this->assertTrue($this->handler->addToGroup('user1', $groupId, true)); + + $members = $this->handler->getGroupMembers($groupId); + + $this->assertCount(2, $members); + + $this->assertEquals('user1', $members[0]['user_id']); + $this->assertEquals($groupId, $members[0]['group_id']); + $this->assertTrue($members[0]['is_admin']); + + $this->assertEquals('user2', $members[1]['user_id']); + $this->assertEquals($groupId, $members[1]['group_id']); + $this->assertFalse($members[1]['is_admin']); + + // add again returns false + $this->assertFalse($this->handler->addToGroup('user1', $groupId, true)); + } + + public function testRemoveFromGroup() { + $groupId = $this->handler->createGroup('my_group', 'My Group'); + $groupId2 = $this->handler->createGroup('my_group2', 'My Group Two'); + + $this->handler->addToGroup('user2', $groupId, false); + $this->handler->addToGroup('user1', $groupId, true); + $this->handler->addToGroup('user2', $groupId2, false); + + $this->assertTrue($this->handler->removeFromGroup('user2', $groupId)); + + $members = $this->handler->getGroupMembers($groupId); + $this->assertCount(1, $members); + + $this->assertEquals('user1', $members[0]['user_id']); + $this->assertEquals($groupId, $members[0]['group_id']); + $this->assertTrue($members[0]['is_admin']); + + // member still exists in the other group + $members2 = $this->handler->getGroupMembers($groupId2); + + $this->assertCount(1, $members2); + $this->assertEquals('user2', $members2[0]['user_id']); + $this->assertEquals($groupId2, $members2[0]['group_id']); + $this->assertFalse($members2[0]['is_admin']); + + // remove again returns false + $this->assertFalse($this->handler->removeFromGroup('user2', $groupId)); + } + + public function testDeleteRemovesMembers() { + $groupId = $this->handler->createGroup('my_group', 'My Group'); + + $this->handler->addToGroup('user2', $groupId, false); + $this->handler->addToGroup('user1', $groupId, true); + + $this->assertTrue($this->handler->deleteGroup($groupId)); + + $this->assertFalse($this->handler->inGroup('user1', $groupId)); + $this->assertFalse($this->handler->inGroup('user2', $groupId)); + } + + public function testGetGroupMemberInfo() { + $groupId = $this->handler->createGroup('my_group', 'My Group'); + + $this->handler->addToGroup('user2', $groupId, false); + $this->handler->addToGroup('user1', $groupId, true); + + $member = $this->handler->getGroupMemberInfo($groupId, 'user1'); + + $this->assertEquals('user1', $member['user_id']); + $this->assertEquals($groupId, $member['group_id']); + $this->assertTrue($member['is_admin']); + } + + public function testSetGroupMemberInfo() { + $groupId = $this->handler->createGroup('my_group', 'My Group'); + + $this->handler->addToGroup('user1', $groupId, true); + + $this->assertTrue($this->handler->setGroupMemberInfo($groupId, 'user1', false)); + $member = $this->handler->getGroupMemberInfo($groupId, 'user1'); + $this->assertFalse($member['is_admin']); + + $this->assertTrue($this->handler->setGroupMemberInfo($groupId, 'user1', true)); + $member = $this->handler->getGroupMemberInfo($groupId, 'user1'); + $this->assertTrue($member['is_admin']); + + // setting to same value also returns true + $this->assertTrue($this->handler->setGroupMemberInfo($groupId, 'user1', true)); + } + + public function testInGroup() { + $groupId = $this->handler->createGroup('my_group', 'My Group'); + + $this->handler->addToGroup('user2', $groupId, false); + + $this->assertTrue($this->handler->inGroup('user2', $groupId)); + $this->assertFalse($this->handler->inGroup('user3', $groupId)); + } + + public function testGetUserGroups() { + $groupId = $this->handler->createGroup('my_group', 'My Group'); + $groupId2 = $this->handler->createGroup('my_group2', 'My Group Two'); + + $this->handler->addToGroup('user2', $groupId, false); + $this->handler->addToGroup('user1', $groupId, true); + $this->handler->addToGroup('user2', $groupId2, false); + + $groups = $this->handler->getUserGroups('user2'); + $this->assertCount(2, $groups); + $this->assertEquals($groupId, $groups[0]); + $this->assertEquals($groupId2, $groups[1]); + } + +} diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php new file mode 100644 index 00000000..0b1324d6 --- /dev/null +++ b/tests/unit/bootstrap.php @@ -0,0 +1,37 @@ + + * @author Jörn Friedrich Dreyer + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +if (!defined('PHPUNIT_RUN')) { + define('PHPUNIT_RUN', 1); +} + +require_once __DIR__.'/../../../../lib/base.php'; + +\OC::$composerAutoloader->addPsr4('Test\\', OC::$SERVERROOT . '/tests/lib/', true); + +// Fix for "Autoload path not allowed: .../customgroups/tests/testcase.php" +\OC_App::loadApp('customgroups'); + +if(!class_exists('PHPUnit_Framework_TestCase')) { + require_once('PHPUnit/Autoload.php'); +} + +OC_Hook::clear(); diff --git a/tests/unit/phpunit.xml b/tests/unit/phpunit.xml new file mode 100644 index 00000000..8fad4d2e --- /dev/null +++ b/tests/unit/phpunit.xml @@ -0,0 +1,25 @@ + + + + . + + + + + ../../appinfo + ../../lib + + + + + + + +