chore: Create AccountToTagRepository tests

A RepositoryInterface is added to expose transactionAware method to the service.

Signed-off-by: Rubén D <nuxsmin@syspass.org>
This commit is contained in:
Rubén D
2022-11-20 11:46:11 +01:00
parent 120adb09ee
commit de9d500d85
7 changed files with 262 additions and 78 deletions

View File

@@ -28,6 +28,7 @@ namespace SP\Domain\Account\In;
use SP\Core\Exceptions\ConstraintException;
use SP\Core\Exceptions\QueryException;
use SP\Domain\Account\Services\AccountRequest;
use SP\Domain\Common\Repositories\RepositoryInterface;
use SP\Infrastructure\Database\QueryResult;
/**
@@ -35,7 +36,7 @@ use SP\Infrastructure\Database\QueryResult;
*
* @package SP\Infrastructure\Account\Repositories
*/
interface AccountToTagRepositoryInterface
interface AccountToTagRepositoryInterface extends RepositoryInterface
{
/**
* Devolver las etiquetas de una cuenta
@@ -48,33 +49,24 @@ interface AccountToTagRepositoryInterface
*/
public function getTagsByAccountId(int $id): QueryResult;
/**
* @param AccountRequest $accountRequest
*
* @throws ConstraintException
* @throws QueryException
*/
public function update(AccountRequest $accountRequest): void;
/**
* Eliminar las etiquetas de una cuenta
*
* @param int $id
*
* @return int
* @return bool
* @throws ConstraintException
* @throws QueryException
*/
public function deleteByAccountId(int $id): int;
public function deleteByAccountId(int $id): bool;
/**
* Actualizar las etiquetas de una cuenta
*
* @param AccountRequest $accountRequest
*
* @return int
* @throws ConstraintException
* @throws QueryException
*/
public function add(AccountRequest $accountRequest): int;
public function add(AccountRequest $accountRequest): void;
}

View File

@@ -478,6 +478,7 @@ final class AccountService extends Service implements AccountServiceInterface
*
* @throws QueryException
* @throws ConstraintException
* @throws \SP\Domain\Common\Services\ServiceException
*/
private function updateItems(AccountRequest $accountRequest): void
{
@@ -517,7 +518,12 @@ final class AccountService extends Service implements AccountServiceInterface
if ($accountRequest->tags !== null) {
if (count($accountRequest->tags) > 0) {
$this->accountToTagRepository->update($accountRequest);
$this->accountToTagRepository->transactionAware(
function () use ($accountRequest) {
$this->accountToTagRepository->deleteByAccountId($accountRequest->id);
$this->accountToTagRepository->add($accountRequest);
}
);
} else {
$this->accountToTagRepository->deleteByAccountId($accountRequest->id);
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* sysPass
*
* @author nuxsmin
* @link https://syspass.org
* @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org
*
* This file is part of sysPass.
*
* sysPass is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sysPass 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
namespace SP\Domain\Common\Repositories;
use Closure;
/**
* Interface RepositoryInterface
*/
interface RepositoryInterface
{
/**
* Bubbles a Closure in a database transaction
*
* @param \Closure $closure
*
* @return mixed
* @throws \SP\Domain\Common\Services\ServiceException
* @throws \Exception
*/
public function transactionAware(Closure $closure): mixed;
}

View File

@@ -24,15 +24,13 @@
namespace SP\Infrastructure\Account\Repositories;
use SP\Core\Exceptions\ConstraintException;
use SP\Core\Exceptions\QueryException;
use SP\DataModel\ItemData;
use SP\Domain\Account\In\AccountToTagRepositoryInterface;
use SP\Domain\Account\Services\AccountRequest;
use SP\Infrastructure\Common\Repositories\Repository;
use SP\Infrastructure\Common\Repositories\RepositoryItemTrait;
use SP\Infrastructure\Database\QueryData;
use SP\Infrastructure\Database\QueryResult;
use function SP\__u;
/**
* Class AccountToTagRepository
@@ -49,36 +47,22 @@ final class AccountToTagRepository extends Repository implements AccountToTagRep
* @param int $id
*
* @return QueryResult
* @throws ConstraintException
* @throws QueryException
*/
public function getTagsByAccountId(int $id): QueryResult
{
$query = /** @lang SQL */
'SELECT T.id, T.name
FROM AccountToTag AT
INNER JOIN Tag T ON AT.tagId = T.id
WHERE AT.accountId = ?
ORDER BY T.name';
$query = $this->queryFactory
->newSelect()
->cols([
'Tag.id',
'Tag.name',
])
->from('AccountToTag')
->join('INNER', 'Tag', 'Tag.id == AccountToTag.tagId')
->where('AccountToTag.accountId = :accountId')
->bindValues(['accountId' => $id])
->orderBy(['Tag.name ASC']);
$queryData = new QueryData();
$queryData->setQuery($query);
$queryData->addParam($id);
$queryData->setMapClassName(ItemData::class);
return $this->db->doSelect($queryData);
}
/**
* @param AccountRequest $accountRequest
*
* @throws ConstraintException
* @throws QueryException
*/
public function update(AccountRequest $accountRequest): void
{
$this->deleteByAccountId($accountRequest->id);
$this->add($accountRequest);
return $this->db->doSelect(QueryData::build($query));
}
/**
@@ -86,18 +70,23 @@ final class AccountToTagRepository extends Repository implements AccountToTagRep
*
* @param int $id
*
* @return int
* @throws ConstraintException
* @throws QueryException
* @return bool
* @throws \SP\Core\Exceptions\ConstraintException
* @throws \SP\Core\Exceptions\QueryException
*/
public function deleteByAccountId(int $id): int
public function deleteByAccountId(int $id): bool
{
$queryData = new QueryData();
$queryData->setQuery('DELETE FROM AccountToTag WHERE accountId = ?');
$queryData->addParam($id);
$queryData->setOnErrorMessage(__u('Error while removing the account\'s tags'));
$query = $this->queryFactory
->newDelete()
->from('AccountToTag')
->where('accountId = :accountId')
->bindValues([
'accountId' => $id,
]);
return $this->db->doQuery($queryData)->getAffectedNumRows();
$queryData = QueryData::build($query)->setOnErrorMessage(__u('Error while removing the account\'s tags'));
return $this->db->doQuery($queryData)->getAffectedNumRows() === 1;
}
/**
@@ -105,28 +94,24 @@ final class AccountToTagRepository extends Repository implements AccountToTagRep
*
* @param AccountRequest $accountRequest
*
* @return int
* @throws ConstraintException
* @throws QueryException
* @return void
* @throws \SP\Core\Exceptions\ConstraintException
* @throws \SP\Core\Exceptions\QueryException
*/
public function add(AccountRequest $accountRequest): int
public function add(AccountRequest $accountRequest): void
{
$query = /** @lang SQL */
'INSERT INTO AccountToTag (accountId, tagId) VALUES '.$this->buildParamsFromArray(
$accountRequest->tags,
'(?,?)'
);
$queryData = new QueryData();
$queryData->setQuery($query);
$queryData->setOnErrorMessage(__u('Error while adding the account\'s tags'));
foreach ($accountRequest->tags as $tag) {
$queryData->addParam($accountRequest->id);
$queryData->addParam($tag);
$query = $this->queryFactory
->newInsert()
->into('AccountToTag')
->cols([
'accountId' => $accountRequest->id,
'tagId' => $tag,
]);
$queryData = QueryData::build($query)->setOnErrorMessage(__u('Error while adding the account\'s tags'));
$this->db->doQuery($queryData);
}
return $this->db->doQuery($queryData)->getAffectedNumRows();
}
}
}

View File

@@ -31,6 +31,7 @@ use SP\Core\Context\ContextInterface;
use SP\Core\Events\Event;
use SP\Core\Events\EventDispatcherInterface;
use SP\Core\Events\EventMessage;
use SP\Domain\Common\Repositories\RepositoryInterface;
use SP\Domain\Common\Services\ServiceException;
use SP\Infrastructure\Database\DatabaseInterface;
use function SP\__u;
@@ -41,7 +42,7 @@ use function SP\logger;
*
* @package SP\Infrastructure\Common\Repositories
*/
abstract class Repository
abstract class Repository implements RepositoryInterface
{
protected ContextInterface $context;
protected DatabaseInterface $db;

View File

@@ -0,0 +1,149 @@
<?php
/*
* sysPass
*
* @author nuxsmin
* @link https://syspass.org
* @copyright 2012-2022, Rubén Domínguez nuxsmin@$syspass.org
*
* This file is part of sysPass.
*
* sysPass is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* sysPass 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with sysPass. If not, see <http://www.gnu.org/licenses/>.
*/
namespace SP\Tests\Infrastructure\Account\Repositories;
use Aura\SqlQuery\QueryFactory;
use PHPUnit\Framework\Constraint\Callback;
use PHPUnit\Framework\MockObject\MockObject;
use SP\Domain\Account\Services\AccountRequest;
use SP\Domain\Common\Out\SimpleModel;
use SP\Infrastructure\Account\Repositories\AccountToTagRepository;
use SP\Infrastructure\Database\DatabaseInterface;
use SP\Infrastructure\Database\QueryData;
use SP\Infrastructure\Database\QueryResult;
use SP\Tests\UnitaryTestCase;
/**
* Class AccountToTagRepositoryTest
*/
class AccountToTagRepositoryTest extends UnitaryTestCase
{
private MockObject|DatabaseInterface $database;
private AccountToTagRepository $accountToTagRepository;
public function testGetTagsByAccountId()
{
$id = self::$faker->randomNumber();
$callback = new Callback(
static function (QueryData $arg) use ($id) {
$query = $arg->getQuery();
return $query->getBindValues()['accountId'] === $id
&& $arg->getMapClassName() === SimpleModel::class
&& !empty($query->getStatement());
}
);
$this->database
->expects(self::once())
->method('doSelect')
->with($callback)
->willReturn(new QueryResult());
$this->accountToTagRepository->getTagsByAccountId($id);
}
/**
* @throws \SP\Core\Exceptions\QueryException
* @throws \SP\Core\Exceptions\ConstraintException
*/
public function testDeleteByAccountId()
{
$accountId = self::$faker->randomNumber();
$expected = new QueryResult();
$expected->setAffectedNumRows(1);
$callback = new Callback(
static function (QueryData $arg) use ($accountId) {
$query = $arg->getQuery();
$params = $query->getBindValues();
return $params['accountId'] === $accountId
&& !empty($query->getStatement());
}
);
$this->database
->expects(self::once())
->method('doQuery')
->with($callback)
->willReturn($expected);
$this->assertTrue($this->accountToTagRepository->deleteByAccountId($accountId));
}
/**
* @throws \SP\Core\Exceptions\ConstraintException
* @throws \SP\Core\Exceptions\QueryException
*/
public function testAdd()
{
$accountRequest = new AccountRequest();
$accountRequest->id = self::$faker->randomNumber();
$accountRequest->tags = self::getRandomNumbers(10);
$callbacks = array_map(
function ($tag) use ($accountRequest) {
return [
new Callback(
static function (QueryData $arg) use ($accountRequest, $tag) {
$query = $arg->getQuery();
$params = $query->getBindValues();
return $params['accountId'] === $accountRequest->id
&& $params['tagId'] === $tag
&& !empty($query->getStatement());
}
),
];
},
$accountRequest->tags
);
$this->database
->expects(self::exactly(count($accountRequest->tags)))
->method('doQuery')
->withConsecutive(...$callbacks);
$this->accountToTagRepository->add($accountRequest);
}
protected function setUp(): void
{
parent::setUp();
$this->database = $this->createMock(DatabaseInterface::class);
$queryFactory = new QueryFactory('mysql');
$this->accountToTagRepository = new AccountToTagRepository(
$this->database,
$this->context,
$this->application->getEventDispatcher(),
$queryFactory,
);
}
}

View File

@@ -24,7 +24,6 @@
namespace SP\Tests;
use DG\BypassFinals;
use Faker\Factory;
use Faker\Generator;
@@ -33,6 +32,7 @@ use SP\Core\Application;
use SP\Core\Context\ContextInterface;
use SP\Core\Context\StatelessContext;
use SP\Core\Events\EventDispatcher;
use SP\Domain\Config\ConfigInterface;
use SP\Domain\Config\Services\ConfigBackupService;
use SP\Domain\Config\Services\ConfigFileService;
use SP\Domain\User\Services\UserLoginResponse;
@@ -44,13 +44,15 @@ use SP\Infrastructure\File\XmlHandler;
*/
abstract class UnitaryTestCase extends TestCase
{
protected static Generator $faker;
protected ConfigFileService $config;
protected Application $application;
protected ContextInterface $context;
protected static Generator $faker;
protected ConfigInterface $config;
protected Application $application;
protected ContextInterface $context;
public static function setUpBeforeClass(): void
{
defined('APP_ROOT') || die();
BypassFinals::enable();
BypassFinals::setWhitelist([APP_ROOT.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'*']);
@@ -97,4 +99,9 @@ abstract class UnitaryTestCase extends TestCase
return new Application($config, $this->createStub(EventDispatcher::class), $this->context);
}
public static function getRandomNumbers(int $count): array
{
return array_map(static fn() => self::$faker->randomNumber(), range(0, $count - 1));
}
}