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..fd4b02f0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,60 @@ +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" + - cd apps/$APP_NAME/ + + # 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/app.php b/appinfo/app.php new file mode 100644 index 00000000..9ccd6640 --- /dev/null +++ b/appinfo/app.php @@ -0,0 +1,23 @@ + + * + * @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 + * + */ + +$app = new \OCA\CustomGroups\Application(); +$app->registerGroupBackend(); diff --git a/appinfo/database.xml b/appinfo/database.xml new file mode 100644 index 00000000..18afe3f0 --- /dev/null +++ b/appinfo/database.xml @@ -0,0 +1,80 @@ + + + *dbname* + true + false + utf8 + + *dbprefix*custom_group + + + + group_id + integer + 0 + true + 1 + 4 + + + + uri + text + 255 + + + + display_name + text + + 64 + + + cg_uri_index + true + + uri + ascending + + + +
+ + *dbprefix*custom_group_member + + + group_id + integer + 0 + true + 4 + + + user_id + text + + true + 64 + + + is_admin + integer + 1 + false + + + cg_gid_uid_index + true + true + + group_id + ascending + + + user_id + ascending + + + +
+
diff --git a/appinfo/info.xml b/appinfo/info.xml index 42b44810..18cfc6d7 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 + 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..7492ed5c --- /dev/null +++ b/lib/CustomGroupsBackend.php @@ -0,0 +1,172 @@ + + * + * @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 CustomGroupsManager */ + private $manager; + + public function __construct( + CustomGroupsManager $manager + ) { + $this->manager = $manager; + } + + /** + * 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->manager->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->manager->getUserGroups($uid); + return array_map(function($numericGroupId) { + return $this->formatGroupId($numericGroupId); + }, $groups); + } + + /** + * Returns a list with all groups + * + * @param string $search + * @param int $limit + * @param int $offset + * @return array an array of group names + * + */ + public function getGroups($search = '', $limit = -1, $offset = 0) { + $groups = $this->manager->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->manager->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/CustomGroupsManager.php b/lib/CustomGroupsManager.php new file mode 100644 index 00000000..4a6eedb6 --- /dev/null +++ b/lib/CustomGroupsManager.php @@ -0,0 +1,321 @@ + + * + * @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; + +/** + * Manager for custom group + */ +class CustomGroupsManager { + + /** @var IDBConnection */ + private $dbConn; + + public function __construct(IDBConnection $dbConn) { + $this->dbConn = $dbConn; + } + + /** + * 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 + */ + 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 + */ + 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 group display names + * + * @param string $search search string + * @param int $limit + * @param int $offset + * @return array an array of group info + */ + 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 !== -1) { + $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 + */ + 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 + */ + 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 + */ + 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 + */ + public function getGroups() { + return $this->searchGroups(); + } + + /** + * Creates a new group + * + * @param string $displayName display name + * @return int group id + */ + 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) { + 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 + */ + public function deleteGroup($gid) { + // 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(); + + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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 + */ + 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; + } + + 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..29ac68d8 --- /dev/null +++ b/tests/unit/CustomGroupsBackendTest.php @@ -0,0 +1,149 @@ + + * + * @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\CustomGroupsManager; +use OCA\CustomGroups\CustomGroupsBackend; +use OCP\GroupInterface; + +/** + * Class CustomGroupsBackendTest + * + * @group DB + * + * @package OCA\CustomGroups\Tests\Unit + */ +class CustomGroupsBackendTest extends \Test\TestCase { + const GROUP_ID_PREFIX = CustomGroupsBackend::GROUP_ID_PREFIX; + + /** + * @var CustomGroupsManager + */ + private $manager; + + public function setUp() { + $this->manager = $this->createMock(CustomGroupsManager::class); + $this->backend = new CustomGroupsBackend($this->manager); + } + + 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->manager->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->manager->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->manager->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->manager->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->manager->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->manager->expects($this->never())->method('getGroup'); + $this->manager->expects($this->never())->method('getGroupMembers'); + $this->assertEquals([], $this->backend->usersInGroup(self::GROUP_ID_PREFIX . '1')); + } + + public function testEmpty2() { + } +} diff --git a/tests/unit/CustomGroupsManagerTest.php b/tests/unit/CustomGroupsManagerTest.php new file mode 100644 index 00000000..f6f242ba --- /dev/null +++ b/tests/unit/CustomGroupsManagerTest.php @@ -0,0 +1,284 @@ + + * + * @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\CustomGroupsManager; + +/** + * Class CustomGroupsManagerTest + * + * @group DB + * + * @package OCA\CustomGroups\Tests\Unit + */ +class CustomGroupsManagerTest extends \Test\TestCase { + + /** + * @var IDBConnection + */ + private $connection; + + /** + * @var CustomGroupsManager + */ + private $manager; + + public function setUp() { + $this->connection = \OC::$server->getDatabaseConnection(); + $this->manager = new CustomGroupsManager($this->connection); + } + + public function tearDown() { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('custom_group_member')->execute(); + $qb->delete('custom_group')->execute(); + } + + public function testCreateGroup() { + $this->assertNotNull($this->manager->createGroup('my_group', 'My Group')); + // recreating returns null + $this->assertNull($this->manager->createGroup('my_group', 'My Group')); + } + + public function testCreateGroupDuplicateUri() { + $this->assertNotNull($this->manager->createGroup('my_group', 'My Group')); + $this->assertNull($this->manager->createGroup('my_group', 'Different display name')); + } + + public function testDeleteGroup() { + $groupId = $this->manager->createGroup('my_group', 'My Group'); + $this->assertTrue($this->manager->deleteGroup($groupId)); + + $this->assertNull($this->manager->getGroup($groupId)); + + $this->assertFalse($this->manager->deleteGroup($groupId)); + } + + public function testSearchGroups() { + $group1Id = $this->manager->createGroup('my_group_1', 'My One Group'); + $group2Id = $this->manager->createGroup('my_group_2', 'My Group Two'); + $group3Id = $this->manager->createGroup('my_group_3', 'AA One'); + + $results = $this->manager->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->manager->createGroup('my_group_' . $num, 'My Group ' . $num); + } + + $results = $this->manager->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->manager->searchGroups('Group', 100, 5); + $this->assertCount(25, $results); + } + + public function testGetGroup() { + $groupId = $this->manager->createGroup('my_group', 'My Group'); + $this->assertNotNull($groupId); + + $groupInfo = $this->manager->getGroup($groupId); + $this->assertEquals($groupId, $groupInfo['group_id']); + $this->assertEquals('My Group', $groupInfo['display_name']); + $this->assertEquals('my_group', $groupInfo['uri']); + + $this->assertNull($this->manager->getGroup(-100)); + } + + public function testGetGroupByUri() { + $groupId = $this->manager->createGroup('my_group', 'My Group'); + $this->assertNotNull($groupId); + + $groupInfo = $this->manager->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->manager->getGroupByUri('unexist')); + } + + public function testGetGroups() { + $group1Id = $this->manager->createGroup('my_group_1', 'My One Group'); + $group2Id = $this->manager->createGroup('my_group_2', 'My Group Two'); + $group3Id = $this->manager->createGroup('my_group_3', 'AA One'); + + $results = $this->manager->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->manager->createGroup('my_group', 'My Group'); + + $this->assertTrue($this->manager->addToGroup('user2', $groupId, false)); + $this->assertTrue($this->manager->addToGroup('user1', $groupId, true)); + + $members = $this->manager->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->manager->addToGroup('user1', $groupId, true)); + } + + public function testRemoveFromGroup() { + $groupId = $this->manager->createGroup('my_group', 'My Group'); + $groupId2 = $this->manager->createGroup('my_group2', 'My Group Two'); + + $this->manager->addToGroup('user2', $groupId, false); + $this->manager->addToGroup('user1', $groupId, true); + $this->manager->addToGroup('user2', $groupId2, false); + + $this->assertTrue($this->manager->removeFromGroup('user2', $groupId)); + + $members = $this->manager->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->manager->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->manager->removeFromGroup('user2', $groupId)); + } + + public function testDeleteRemovesMembers() { + $groupId = $this->manager->createGroup('my_group', 'My Group'); + + $this->manager->addToGroup('user2', $groupId, false); + $this->manager->addToGroup('user1', $groupId, true); + + $this->assertTrue($this->manager->deleteGroup($groupId)); + + $this->assertFalse($this->manager->inGroup('user1', $groupId)); + $this->assertFalse($this->manager->inGroup('user2', $groupId)); + } + + public function testGetGroupMemberInfo() { + $groupId = $this->manager->createGroup('my_group', 'My Group'); + + $this->manager->addToGroup('user2', $groupId, false); + $this->manager->addToGroup('user1', $groupId, true); + + $member = $this->manager->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->manager->createGroup('my_group', 'My Group'); + + $this->manager->addToGroup('user1', $groupId, true); + + $this->assertTrue($this->manager->setGroupMemberInfo($groupId, 'user1', false)); + $member = $this->manager->getGroupMemberInfo($groupId, 'user1'); + $this->assertFalse($member['is_admin']); + + $this->assertTrue($this->manager->setGroupMemberInfo($groupId, 'user1', true)); + $member = $this->manager->getGroupMemberInfo($groupId, 'user1'); + $this->assertTrue($member['is_admin']); + + // setting to same value also returns true + $this->assertTrue($this->manager->setGroupMemberInfo($groupId, 'user1', true)); + } + + public function testInGroup() { + $groupId = $this->manager->createGroup('my_group', 'My Group'); + + $this->manager->addToGroup('user2', $groupId, false); + + $this->assertTrue($this->manager->inGroup('user2', $groupId)); + $this->assertFalse($this->manager->inGroup('user3', $groupId)); + } + + public function testGetUserGroups() { + $groupId = $this->manager->createGroup('my_group', 'My Group'); + $groupId2 = $this->manager->createGroup('my_group2', 'My Group Two'); + + $this->manager->addToGroup('user2', $groupId, false); + $this->manager->addToGroup('user1', $groupId, true); + $this->manager->addToGroup('user2', $groupId2, false); + + $groups = $this->manager->getUserGroups('user2'); + $this->assertCount(2, $groups); + $this->assertEquals($groupId, $groups[0]); + $this->assertEquals($groupId2, $groups[1]); + } + + public function testEmpty() { + } + +} 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 + + + + + + + +