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
+
+
+
+
+
+
+
+