diff --git a/lib/SP/Account/AccountSearchFilter.php b/lib/SP/Account/AccountSearchFilter.php index 99e11ab7..8b91122c 100644 --- a/lib/SP/Account/AccountSearchFilter.php +++ b/lib/SP/Account/AccountSearchFilter.php @@ -74,7 +74,7 @@ class AccountSearchFilter /** * @var array */ - private $tagsId = []; + private $tagsId; /** * @var int */ @@ -265,7 +265,7 @@ class AccountSearchFilter */ public function getTagsId() { - return $this->tagsId; + return $this->tagsId ?: []; } /** @@ -281,12 +281,20 @@ class AccountSearchFilter return $this; } + /** + * @return bool + */ + public function hasTags() + { + return !empty($this->tagsId); + } + /** * @return QueryCondition */ public function getStringFilters() { - return $this->stringFilters; + return $this->stringFilters ?: new QueryCondition(); } /** @@ -394,7 +402,7 @@ class AccountSearchFilter */ public function getFilterOperator() { - return $this->filterOperator; + return $this->filterOperator ?: QueryCondition::CONDITION_AND; } /** @@ -404,4 +412,24 @@ class AccountSearchFilter { $this->filterOperator = $filterOperator; } + + /** + * Resets internal variables + */ + public function reset() + { + self::$queryNumRows = null; + $this->categoryId = null; + $this->clientId = null; + $this->filterOperator = null; + $this->globalSearch = false; + $this->txtSearch = null; + $this->cleanTxtSearch = null; + $this->tagsId = null; + $this->limitCount = null; + $this->sortViews = null; + $this->searchFavorites = false; + $this->sortOrder = self::SORT_DEFAULT; + $this->sortKey = self::SORT_DIR_ASC; + } } \ No newline at end of file diff --git a/lib/SP/Mvc/Model/QueryCondition.php b/lib/SP/Mvc/Model/QueryCondition.php index 2d91dade..4254adc8 100644 --- a/lib/SP/Mvc/Model/QueryCondition.php +++ b/lib/SP/Mvc/Model/QueryCondition.php @@ -69,7 +69,7 @@ class QueryCondition throw new \RuntimeException(__u('Tipo de filtro inválido')); } - return $this->hasFilters() ? implode($type, $this->query) : null; + return $this->hasFilters() ? '(' . implode($type, $this->query) . ')' : null; } /** diff --git a/lib/SP/Mvc/Model/QueryJoin.php b/lib/SP/Mvc/Model/QueryJoin.php new file mode 100644 index 00000000..efaba007 --- /dev/null +++ b/lib/SP/Mvc/Model/QueryJoin.php @@ -0,0 +1,90 @@ +. + */ + +namespace SP\Mvc\Model; + +/** + * Class QueryJoin + * + * @package SP\Mvc\Model + */ +class QueryJoin +{ + /** + * @var array + */ + protected $join = []; + /** + * @var array + */ + protected $param = []; + + /** + * @param string $join + * @param array $params + * @return QueryJoin + */ + public function addJoin($join, array $params = null) + { + $this->join[] = $join; + + if ($params !== null) { + $this->param = array_merge($this->param, $params); + } + + return $this; + } + + /** + * @return string|null + */ + public function getJoins() + { + return $this->hasJoins() ? implode(PHP_EOL, $this->join) : null; + } + + /** + * @return bool + */ + public function hasJoins() + { + return !empty($this->join); + } + + /** + * @return array + */ + public function getParams() + { + return $this->param; + } + + /** + * @return int + */ + public function getJoinsCount() + { + return count($this->join); + } +} \ No newline at end of file diff --git a/lib/SP/Repositories/Account/AccountRepository.php b/lib/SP/Repositories/Account/AccountRepository.php index 8e5174c7..80f4c3b6 100644 --- a/lib/SP/Repositories/Account/AccountRepository.php +++ b/lib/SP/Repositories/Account/AccountRepository.php @@ -39,6 +39,7 @@ use SP\DataModel\ItemData; use SP\DataModel\ItemSearchData; use SP\Mvc\Model\QueryAssignment; use SP\Mvc\Model\QueryCondition; +use SP\Mvc\Model\QueryJoin; use SP\Repositories\Repository; use SP\Repositories\RepositoryItemInterface; use SP\Repositories\RepositoryItemTrait; @@ -579,68 +580,61 @@ class AccountRepository extends Repository implements RepositoryItemInterface */ public function getByFilter(AccountSearchFilter $accountSearchFilter) { - $queryFilterCommon = new QueryCondition(); - $queryFilterSelect = new QueryCondition(); + $queryFilters = new QueryCondition(); // Sets the search text depending on if special search filters are being used $searchText = $accountSearchFilter->getCleanTxtSearch(); if (!empty($searchText)) { - $searchText = '%' . $searchText . '%'; - - $queryFilterCommon->addFilter('A.name LIKE ? OR A.login LIKE ? OR A.url LIKE ? OR A.notes LIKE ?', [$searchText, $searchText, $searchText, $searchText]); + $queryFilters->addFilter('A.name LIKE ? OR A.login LIKE ? OR A.url LIKE ? OR A.notes LIKE ?', array_fill(0, 4, '%' . $searchText . '%')); } // Gets special search filters $stringFilters = $accountSearchFilter->getStringFilters(); if ($stringFilters->hasFilters()) { - $queryFilterCommon->addFilter($stringFilters->getFilters(), $stringFilters->getParams()); + $queryFilters->addFilter($stringFilters->getFilters(), $stringFilters->getParams()); } if (!empty($accountSearchFilter->getCategoryId())) { - $queryFilterSelect->addFilter('A.categoryId = ?', [$accountSearchFilter->getCategoryId()]); + $queryFilters->addFilter('A.categoryId = ?', [$accountSearchFilter->getCategoryId()]); } if (!empty($accountSearchFilter->getClientId())) { - $queryFilterSelect->addFilter('A.clientId = ?', [$accountSearchFilter->getClientId()]); - } - - $tagsId = $accountSearchFilter->getTagsId(); - $numTags = count($tagsId); - - if ($numTags > 0) { - $queryFilterSelect->addFilter('A.id IN (SELECT accountId FROM AccountToTag WHERE tagId IN (' . str_repeat('?,', $numTags - 1) . '?' . '))', $tagsId); + $queryFilters->addFilter('A.clientId = ?', [$accountSearchFilter->getClientId()]); } $where = []; - if ($queryFilterCommon->hasFilters()) { - $where[] = $queryFilterCommon->getFilters($accountSearchFilter->getFilterOperator()); - } - - if ($queryFilterSelect->hasFilters()) { - $where[] = $queryFilterSelect->getFilters(); - } - $queryFilterUser = AccountUtil::getAccountFilterUser($this->context, $accountSearchFilter->getGlobalSearch()); if ($queryFilterUser->hasFilters()) { $where[] = $queryFilterUser->getFilters(); } - $join = ['query' => [], 'param' => []]; + $queryJoins = new QueryJoin(); if ($accountSearchFilter->isSearchFavorites() === true) { - $join['query'][] = 'INNER JOIN AccountToFavorite AF ON (AF.accountId = A.id AND AF.userId = ?)'; - $join['param'][] = $this->context->getUserData()->getId(); + $queryJoins->addJoin('INNER JOIN AccountToFavorite AF ON (AF.accountId = A.id AND AF.userId = ?)', [$this->context->getUserData()->getId()]); + } + + if ($accountSearchFilter->hasTags()) { + $queryJoins->addJoin('INNER JOIN AccountToTag AT ON AT.accountId = A.id'); + + foreach ($accountSearchFilter->getTagsId() as $tag) { + $queryFilters->addFilter('AT.tagId = ?', [$tag]); + } + } + + if ($queryFilters->hasFilters()) { + $where[] = $queryFilters->getFilters($accountSearchFilter->getFilterOperator()); } $queryData = new QueryData(); $queryData->setWhere($where); - $queryData->setParams(array_merge($join['param'], $queryFilterCommon->getParams(), $queryFilterSelect->getParams(), $queryFilterUser->getParams())); + $queryData->setParams(array_merge($queryJoins->getParams(), $queryFilterUser->getParams(), $queryFilters->getParams())); $queryData->setSelect('*'); - $queryData->setFrom('account_search_v A ' . implode(PHP_EOL, $join['query'])); + $queryData->setFrom('account_search_v A ' . $queryJoins->getJoins()); $queryData->setOrder($accountSearchFilter->getOrderString()); if ($accountSearchFilter->getLimitCount() > 0) { diff --git a/lib/SP/Services/Account/AccountSearchService.php b/lib/SP/Services/Account/AccountSearchService.php index 9fd80a01..933c8fca 100644 --- a/lib/SP/Services/Account/AccountSearchService.php +++ b/lib/SP/Services/Account/AccountSearchService.php @@ -120,7 +120,7 @@ class AccountSearchService extends Service /** * @var string */ - private $filterOperator = QueryCondition::CONDITION_OR; + private $filterOperator; /** * Procesar los resultados de la búsqueda y crear la variable que contiene los datos de cada cuenta diff --git a/lib/SP/Services/Install/Installer.php b/lib/SP/Services/Install/Installer.php index c5a32c3d..2052dd9a 100644 --- a/lib/SP/Services/Install/Installer.php +++ b/lib/SP/Services/Install/Installer.php @@ -56,7 +56,7 @@ class Installer extends Service */ const VERSION = [3, 0, 0]; const VERSION_TEXT = '3.0-beta'; - const BUILD = 18053001; + const BUILD = 18053101; /** * @var ConfigService diff --git a/tests/AccountRepositoryTest.php b/tests/AccountRepositoryTest.php index 6e76fdd9..7e1ced44 100644 --- a/tests/AccountRepositoryTest.php +++ b/tests/AccountRepositoryTest.php @@ -24,25 +24,22 @@ namespace SP\Tests; -use DI\ContainerBuilder; use DI\DependencyException; -use Doctrine\Common\Cache\ArrayCache; use PHPUnit\DbUnit\Database\Connection; use PHPUnit\DbUnit\Database\DefaultConnection; use PHPUnit\DbUnit\DataSet\IDataSet; use PHPUnit\DbUnit\TestCaseTrait; use PHPUnit\Framework\TestCase; use SP\Account\AccountRequest; -use SP\Config\ConfigData; -use SP\Core\Context\ContextInterface; +use SP\Account\AccountSearchFilter; use SP\Core\Crypt\Crypt; use SP\Core\Exceptions\SPException; use SP\DataModel\AccountVData; +use SP\DataModel\Dto\AccountSearchResponse; use SP\DataModel\ItemSearchData; use SP\Mvc\Model\QueryCondition; use SP\Repositories\Account\AccountRepository; use SP\Services\Account\AccountPasswordRequest; -use SP\Services\User\UserLoginResponse; use SP\Storage\DatabaseConnectionData; use SP\Storage\MySQLHandler; @@ -82,33 +79,10 @@ class AccountRepositoryTest extends TestCase */ public static function setUpBeforeClass() { - // Instancia del contenedor de dependencias con las definiciones de los objetos necesarios - // para la aplicación - $builder = new ContainerBuilder(); - $builder->setDefinitionCache(new ArrayCache()); - $builder->addDefinitions(APP_ROOT . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'Definitions.php'); - $dic = $builder->build(); + $dic = setupContext(); - // Inicializar el contexto - $context = $dic->get(ContextInterface::class); - $context->initialize(); - $context->setConfig(new ConfigData()); - - $userData = new UserLoginResponse(); - $userData->setId(1); - $userData->setUserGroupId(1); - $userData->setIsAdminApp(1); - - $context->setUserData($userData); - - self::$databaseConnectionData = (new DatabaseConnectionData()) - ->setDbHost('172.17.0.3') - ->setDbName('syspass') - ->setDbUser('root') - ->setDbPass('syspass'); - - $dic->set(ConfigData::class, $context->getConfig()); - $dic->set(DatabaseConnectionData::class, self::$databaseConnectionData); + // Datos de conexión a la BBDD + self::$databaseConnectionData = $dic->get(DatabaseConnectionData::class); // Inicializar el repositorio self::$accountRepository = $dic->get(AccountRepository::class); @@ -270,30 +244,32 @@ class AccountRepositoryTest extends TestCase */ public function testSearch() { + // Comprobar búsqueda con el texto Google Inc $itemSearchData = new ItemSearchData(); - $itemSearchData->setSeachString('Google'); + $itemSearchData->setSeachString('Google Inc'); $itemSearchData->setLimitCount(10); $search = self::$accountRepository->search($itemSearchData); - $this->assertCount(3, $search); + $this->assertCount(2, $search); $this->assertArrayHasKey('count', $search); - $this->assertEquals(2, $search['count']); + $this->assertEquals(1, $search['count']); $this->assertInstanceOf(\stdClass::class, $search[0]); $this->assertEquals(1, $search[0]->id); $this->assertEquals('Google', $search[0]->name); + // Comprobar búsqueda con el texto Apple $itemSearchData = new ItemSearchData(); - $itemSearchData->setSeachString('Google'); + $itemSearchData->setSeachString('Apple'); $itemSearchData->setLimitCount(1); $search = self::$accountRepository->search($itemSearchData); $this->assertCount(2, $search); $this->assertArrayHasKey('count', $search); - $this->assertEquals(2, $search['count']); + $this->assertEquals(1, $search['count']); $this->assertInstanceOf(\stdClass::class, $search[0]); - $this->assertEquals(1, $search[0]->id); - $this->assertEquals('Google', $search[0]->name); + $this->assertEquals(2, $search[0]->id); + $this->assertEquals('Apple', $search[0]->name); } /** @@ -389,24 +365,67 @@ class AccountRepositoryTest extends TestCase $this->markTestSkipped(); } + /** + * Comprobar las cuentas devueltas para un filtro de usuario + */ public function testGetForUser() { -// self::$accountRepository->getForUser(); + $queryCondition = new QueryCondition(); + $queryCondition->addFilter('A.isPrivate = 1'); + + $this->assertCount(0, self::$accountRepository->getForUser($queryCondition)); } + /** + * Comprobar las cuentas devueltas para obtener los datos de las claves + */ public function testGetAccountsPassData() { - + $this->assertCount(2, self::$accountRepository->getAccountsPassData()); } + /** + * Comprobar la creación de una cuenta + * + * @throws SPException + * @throws \Defuse\Crypto\Exception\CryptoException + * @throws \SP\Core\Exceptions\ConstraintException + * @throws \SP\Core\Exceptions\QueryException + */ public function testCreate() { + $accountRequest = new AccountRequest(); + $accountRequest->name = 'Prueba 2'; + $accountRequest->login = 'admin'; + $accountRequest->url = 'http://syspass.org'; + $accountRequest->notes = 'notas'; + $accountRequest->userEditId = 1; + $accountRequest->passDateChange = time() + 3600; + $accountRequest->clientId = 1; + $accountRequest->categoryId = 1; + $accountRequest->isPrivate = 0; + $accountRequest->isPrivateGroup = 0; + $accountRequest->parentId = 0; + $accountRequest->userId = 1; + $accountRequest->userGroupId = 2; + $accountRequest->key = Crypt::makeSecuredKey(self::SECURE_KEY_PASSWORD); + $accountRequest->pass = Crypt::encrypt('1234', $accountRequest->key, self::SECURE_KEY_PASSWORD); + // Comprobar registros iniciales + $this->assertEquals(2, $this->conn->getRowCount('Account')); + + self::$accountRepository->create($accountRequest); + + // Comprobar registros finales + $this->assertEquals(3, $this->conn->getRowCount('Account')); } + /** + * No implementado + */ public function testGetByIdBatch() { - + $this->markTestSkipped(); } /** @@ -417,14 +436,90 @@ class AccountRepositoryTest extends TestCase $this->markTestSkipped(); } + /** + * No implementado + */ public function testGetPasswordHistoryForId() { - + $this->markTestSkipped(); } + /** + * Comprobar la búsqueda de cuentas mediante filtros + */ public function testGetByFilter() { + $searchFilter = new AccountSearchFilter(); + $searchFilter->setLimitCount(10); + $searchFilter->setCategoryId(1); + // Comprobar un Id de categoría + $response = self::$accountRepository->getByFilter($searchFilter); + $this->assertInstanceOf(AccountSearchResponse::class, $response); + $this->assertEquals(1, $response->getCount()); + $this->assertCount(1, $response->getData()); + + // Comprobar un Id de categoría no existente + $searchFilter->reset(); + $searchFilter->setLimitCount(10); + $searchFilter->setCategoryId(10); + + $response = self::$accountRepository->getByFilter($searchFilter); + $this->assertInstanceOf(AccountSearchResponse::class, $response); + $this->assertEquals(0, $response->getCount()); + $this->assertCount(0, $response->getData()); + + // Comprobar un Id de cliente + $searchFilter->reset(); + $searchFilter->setLimitCount(10); + $searchFilter->setClientId(1); + + $response = self::$accountRepository->getByFilter($searchFilter); + $this->assertInstanceOf(AccountSearchResponse::class, $response); + $this->assertEquals(1, $response->getCount()); + $this->assertCount(1, $response->getData()); + + // Comprobar un Id de cliente no existente + $searchFilter->reset(); + $searchFilter->setLimitCount(10); + $searchFilter->setClientId(10); + + $response = self::$accountRepository->getByFilter($searchFilter); + $this->assertInstanceOf(AccountSearchResponse::class, $response); + $this->assertEquals(0, $response->getCount()); + $this->assertCount(0, $response->getData()); + + // Comprobar una cadena de texto + $searchFilter->reset(); + $searchFilter->setLimitCount(10); + $searchFilter->setCleanTxtSearch('apple.com'); + + $response = self::$accountRepository->getByFilter($searchFilter); + $this->assertInstanceOf(AccountSearchResponse::class, $response); + $this->assertEquals(1, $response->getCount()); + $this->assertCount(1, $response->getData()); + $this->assertEquals(2, $response->getData()[0]->getId()); + + // Comprobar los favoritos + $searchFilter->reset(); + $searchFilter->setLimitCount(10); + $searchFilter->setSearchFavorites(true); + + $response = self::$accountRepository->getByFilter($searchFilter); + $this->assertInstanceOf(AccountSearchResponse::class, $response); + $this->assertEquals(0, $response->getCount()); + $this->assertCount(0, $response->getData()); + + // Comprobar las etiquetas + $searchFilter->reset(); + $searchFilter->setLimitCount(10); + $searchFilter->setTagsId([1]); + + $response = self::$accountRepository->getByFilter($searchFilter); + $this->assertInstanceOf(AccountSearchResponse::class, $response); + $this->assertEquals(1, $response->getCount()); + $this->assertCount(1, $response->getData()); + $this->assertEquals(1, $response->getData()[0]->getId()); } /** diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 11a7585b..bb3e44a8 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -22,6 +22,15 @@ * along with sysPass. If not, see . */ +namespace SP\Tests; + +use DI\ContainerBuilder; +use Doctrine\Common\Cache\ArrayCache; +use SP\Config\ConfigData; +use SP\Core\Context\ContextInterface; +use SP\Services\User\UserLoginResponse; +use SP\Storage\DatabaseConnectionData; + define('APP_MODULE', 'tests'); define('APP_ROOT', dirname(__DIR__)); @@ -48,4 +57,48 @@ if (!function_exists('gettext')) { { return $str; } +} + +/** + * Configura el contexto de la aplicación para los tests + * + * @throws \DI\DependencyException + * @throws \DI\NotFoundException + * @throws \SP\Core\Context\ContextException + * @return \DI\Container + */ +function setupContext() +{ + // Instancia del contenedor de dependencias con las definiciones de los objetos necesarios + // para la aplicación + $builder = new ContainerBuilder(); + $builder->setDefinitionCache(new ArrayCache()); + $builder->addDefinitions(APP_ROOT . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR . 'Definitions.php'); + $dic = $builder->build(); + + // Inicializar el contexto + $context = $dic->get(ContextInterface::class); + $context->initialize(); + $context->setConfig(new ConfigData()); + + $userData = new UserLoginResponse(); + $userData->setId(1); + $userData->setUserGroupId(1); + $userData->setIsAdminApp(1); + + $context->setUserData($userData); + + $databaseConnectionData = (new DatabaseConnectionData()) + ->setDbHost('172.17.0.2') + ->setDbName('syspass') + ->setDbUser('root') + ->setDbPass('syspass'); + + // Inicializar la configuración + $dic->set(ConfigData::class, $context->getConfig()); + + // Inicializar los datos de conexión a la BBDD + $dic->set(DatabaseConnectionData::class, $databaseConnectionData); + + return $dic; } \ No newline at end of file