Merge pull request from acelaya-forks/feature/short-urls-filtering

Feature/short urls filtering
This commit is contained in:
Alejandro Celaya 2022-01-17 20:33:40 +01:00 committed by GitHub
commit 223339cd61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 281 additions and 138 deletions

View File

@ -32,6 +32,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this
* [#1300](https://github.com/shlinkio/shlink/issues/1300) Changed default ordering for short URLs list, returning always from newest to oldest. * [#1300](https://github.com/shlinkio/shlink/issues/1300) Changed default ordering for short URLs list, returning always from newest to oldest.
* [#1299](https://github.com/shlinkio/shlink/issues/1299) Updated to the latest base docker images, based in PHP 8.1.1, and bumped openswoole to v4.9.1. * [#1299](https://github.com/shlinkio/shlink/issues/1299) Updated to the latest base docker images, based in PHP 8.1.1, and bumped openswoole to v4.9.1.
* [#1282](https://github.com/shlinkio/shlink/issues/1282) Env vars now have precedence over installer options. * [#1282](https://github.com/shlinkio/shlink/issues/1282) Env vars now have precedence over installer options.
* [#1328](https://github.com/shlinkio/shlink/issues/1328) Refactored ShortUrlsRepository to use DTOs in methods with too many arguments.
### Deprecated ### Deprecated
* [#1315](https://github.com/shlinkio/shlink/issues/1315) Deprecated `GET /tags?withStats=true` endpoint. Use `GET /tags/stats` instead. * [#1315](https://github.com/shlinkio/shlink/issues/1315) Deprecated `GET /tags?withStats=true` endpoint. Use `GET /tags/stats` instead.

View File

@ -18,6 +18,11 @@ final class Ordering
return new self($field, $dir ?? self::DEFAULT_DIR); return new self($field, $dir ?? self::DEFAULT_DIR);
} }
public static function emptyInstance(): self
{
return self::fromTuple([null, null]);
}
public function orderField(): ?string public function orderField(): ?string
{ {
return $this->field; return $this->field;

View File

@ -11,12 +11,13 @@ use Doctrine\ORM\QueryBuilder;
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository; use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
use Happyr\DoctrineSpecification\Specification\Specification; use Happyr\DoctrineSpecification\Specification\Specification;
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType; use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\Ordering; use Shlinkio\Shlink\Core\Model\Ordering;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
use function array_column; use function array_column;
@ -26,27 +27,18 @@ use function Functional\contains;
class ShortUrlRepository extends EntitySpecificationRepository implements ShortUrlRepositoryInterface class ShortUrlRepository extends EntitySpecificationRepository implements ShortUrlRepositoryInterface
{ {
/** /**
* @param string[] $tags
* @return ShortUrl[] * @return ShortUrl[]
*/ */
public function findList( public function findList(ShortUrlsListFiltering $filtering): array
?int $limit = null, {
?int $offset = null, $qb = $this->createListQueryBuilder($filtering);
?string $searchTerm = null,
array $tags = [],
?string $tagsMode = null,
?Ordering $orderBy = null,
?DateRange $dateRange = null,
?Specification $spec = null,
): array {
$qb = $this->createListQueryBuilder($searchTerm, $tags, $tagsMode, $dateRange, $spec);
$qb->select('DISTINCT s') $qb->select('DISTINCT s')
->setMaxResults($limit) ->setMaxResults($filtering->limit())
->setFirstResult($offset); ->setFirstResult($filtering->offset());
// In case the ordering has been specified, the query could be more complex. Process it // In case the ordering has been specified, the query could be more complex. Process it
if ($orderBy?->hasOrderField()) { if ($filtering->orderBy()->hasOrderField()) {
return $this->processOrderByForList($qb, $orderBy); return $this->processOrderByForList($qb, $filtering->orderBy());
} }
// With no explicit order by, fallback to dateCreated-DESC // With no explicit order by, fallback to dateCreated-DESC
@ -77,30 +69,21 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
return $qb->getQuery()->getResult(); return $qb->getQuery()->getResult();
} }
public function countList( public function countList(ShortUrlsCountFiltering $filtering): int
?string $searchTerm = null, {
array $tags = [], $qb = $this->createListQueryBuilder($filtering);
?string $tagsMode = null,
?DateRange $dateRange = null,
?Specification $spec = null,
): int {
$qb = $this->createListQueryBuilder($searchTerm, $tags, $tagsMode, $dateRange, $spec);
$qb->select('COUNT(DISTINCT s)'); $qb->select('COUNT(DISTINCT s)');
return (int) $qb->getQuery()->getSingleScalarResult(); return (int) $qb->getQuery()->getSingleScalarResult();
} }
private function createListQueryBuilder( private function createListQueryBuilder(ShortUrlsCountFiltering $filtering): QueryBuilder
?string $searchTerm, {
array $tags,
?string $tagsMode,
?DateRange $dateRange,
?Specification $spec,
): QueryBuilder {
$qb = $this->getEntityManager()->createQueryBuilder(); $qb = $this->getEntityManager()->createQueryBuilder();
$qb->from(ShortUrl::class, 's') $qb->from(ShortUrl::class, 's')
->where('1=1'); ->where('1=1');
$dateRange = $filtering->dateRange();
if ($dateRange?->startDate() !== null) { if ($dateRange?->startDate() !== null) {
$qb->andWhere($qb->expr()->gte('s.dateCreated', ':startDate')); $qb->andWhere($qb->expr()->gte('s.dateCreated', ':startDate'));
$qb->setParameter('startDate', $dateRange->startDate(), ChronosDateTimeType::CHRONOS_DATETIME); $qb->setParameter('startDate', $dateRange->startDate(), ChronosDateTimeType::CHRONOS_DATETIME);
@ -110,6 +93,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
$qb->setParameter('endDate', $dateRange->endDate(), ChronosDateTimeType::CHRONOS_DATETIME); $qb->setParameter('endDate', $dateRange->endDate(), ChronosDateTimeType::CHRONOS_DATETIME);
} }
$searchTerm = $filtering->searchTerm();
$tags = $filtering->tags();
// Apply search term to every searchable field if not empty // Apply search term to every searchable field if not empty
if (! empty($searchTerm)) { if (! empty($searchTerm)) {
// Left join with tags only if no tags were provided. In case of tags, an inner join will be done later // Left join with tags only if no tags were provided. In case of tags, an inner join will be done later
@ -131,18 +116,18 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
// Filter by tags if provided // Filter by tags if provided
if (! empty($tags)) { if (! empty($tags)) {
$tagsMode = $tagsMode ?? ShortUrlsParams::TAGS_MODE_ANY; $tagsMode = $filtering->tagsMode() ?? ShortUrlsParams::TAGS_MODE_ANY;
$tagsMode === ShortUrlsParams::TAGS_MODE_ANY $tagsMode === ShortUrlsParams::TAGS_MODE_ANY
? $qb->join('s.tags', 't')->andWhere($qb->expr()->in('t.name', $tags)) ? $qb->join('s.tags', 't')->andWhere($qb->expr()->in('t.name', $tags))
: $this->joinAllTags($qb, $tags); : $this->joinAllTags($qb, $tags);
} }
$this->applySpecification($qb, $spec, 's'); $this->applySpecification($qb, $filtering->apiKey()?->spec(), 's');
return $qb; return $qb;
} }
public function findOneWithDomainFallback(string $shortCode, ?string $domain = null): ?ShortUrl public function findOneWithDomainFallback(ShortUrlIdentifier $identifier): ?ShortUrl
{ {
// When ordering DESC, Postgres puts nulls at the beginning while the rest of supported DB engines put them at // When ordering DESC, Postgres puts nulls at the beginning while the rest of supported DB engines put them at
// the bottom // the bottom
@ -161,8 +146,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
$query = $this->getEntityManager()->createQuery($dql); $query = $this->getEntityManager()->createQuery($dql);
$query->setMaxResults(1) $query->setMaxResults(1)
->setParameters([ ->setParameters([
'shortCode' => $shortCode, 'shortCode' => $identifier->shortCode(),
'domain' => $domain, 'domain' => $identifier->domain(),
]); ]);
// Since we ordered by domain, we will have first the URL matching provided domain, followed by the one // Since we ordered by domain, we will have first the URL matching provided domain, followed by the one

View File

@ -7,35 +7,20 @@ namespace Shlinkio\Shlink\Core\Repository;
use Doctrine\Persistence\ObjectRepository; use Doctrine\Persistence\ObjectRepository;
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryInterface; use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepositoryInterface;
use Happyr\DoctrineSpecification\Specification\Specification; use Happyr\DoctrineSpecification\Specification\Specification;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\Ordering;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificationRepositoryInterface interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificationRepositoryInterface
{ {
public function findList( public function findList(ShortUrlsListFiltering $filtering): array;
?int $limit = null,
?int $offset = null,
?string $searchTerm = null,
array $tags = [],
?string $tagsMode = null,
?Ordering $orderBy = null,
?DateRange $dateRange = null,
?Specification $spec = null,
): array;
public function countList( public function countList(ShortUrlsCountFiltering $filtering): int;
?string $searchTerm = null,
array $tags = [],
?string $tagsMode = null,
?DateRange $dateRange = null,
?Specification $spec = null,
): int;
public function findOneWithDomainFallback(string $shortCode, ?string $domain = null): ?ShortUrl; public function findOneWithDomainFallback(ShortUrlIdentifier $identifier): ?ShortUrl;
public function findOne(ShortUrlIdentifier $identifier, ?Specification $spec = null): ?ShortUrl; public function findOne(ShortUrlIdentifier $identifier, ?Specification $spec = null): ?ShortUrl;

View File

@ -39,7 +39,7 @@ class ShortUrlResolver implements ShortUrlResolverInterface
{ {
/** @var ShortUrlRepository $shortUrlRepo */ /** @var ShortUrlRepository $shortUrlRepo */
$shortUrlRepo = $this->em->getRepository(ShortUrl::class); $shortUrlRepo = $this->em->getRepository(ShortUrl::class);
$shortUrl = $shortUrlRepo->findOneWithDomainFallback($identifier->shortCode(), $identifier->domain()); $shortUrl = $shortUrlRepo->findOneWithDomainFallback($identifier);
if (! $shortUrl?->isEnabled()) { if (! $shortUrl?->isEnabled()) {
throw ShortUrlNotFoundException::fromNotFound($identifier); throw ShortUrlNotFoundException::fromNotFound($identifier);
} }

View File

@ -12,10 +12,10 @@ use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
use Shlinkio\Shlink\Core\Model\ShortUrlEdit; use Shlinkio\Shlink\Core\Model\ShortUrlEdit;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlTitleResolutionHelperInterface; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlTitleResolutionHelperInterface;
use Shlinkio\Shlink\Core\ShortUrl\Paginator\Adapter\ShortUrlRepositoryAdapter;
use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface; use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;

View File

@ -2,11 +2,13 @@
declare(strict_types=1); declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Paginator\Adapter; namespace Shlinkio\Shlink\Core\ShortUrl\Paginator\Adapter;
use Pagerfanta\Adapter\AdapterInterface; use Pagerfanta\Adapter\AdapterInterface;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ShortUrlRepositoryAdapter implements AdapterInterface class ShortUrlRepositoryAdapter implements AdapterInterface
@ -21,25 +23,12 @@ class ShortUrlRepositoryAdapter implements AdapterInterface
public function getSlice(int $offset, int $length): iterable public function getSlice(int $offset, int $length): iterable
{ {
return $this->repository->findList( return $this->repository->findList(
$length, ShortUrlsListFiltering::fromLimitsAndParams($length, $offset, $this->params, $this->apiKey),
$offset,
$this->params->searchTerm(),
$this->params->tags(),
$this->params->tagsMode(),
$this->params->orderBy(),
$this->params->dateRange(),
$this->apiKey?->spec(),
); );
} }
public function getNbResults(): int public function getNbResults(): int
{ {
return $this->repository->countList( return $this->repository->countList(ShortUrlsCountFiltering::fromParams($this->params, $this->apiKey));
$this->params->searchTerm(),
$this->params->tags(),
$this->params->tagsMode(),
$this->params->dateRange(),
$this->apiKey?->spec(),
);
} }
} }

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Persistence;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ShortUrlsCountFiltering
{
public function __construct(
private ?string $searchTerm = null,
private array $tags = [],
private ?string $tagsMode = null,
private ?DateRange $dateRange = null,
private ?ApiKey $apiKey = null,
) {
}
public static function fromParams(ShortUrlsParams $params, ?ApiKey $apiKey): self
{
return new self($params->searchTerm(), $params->tags(), $params->tagsMode(), $params->dateRange(), $apiKey);
}
public function searchTerm(): ?string
{
return $this->searchTerm;
}
public function tags(): array
{
return $this->tags;
}
public function tagsMode(): ?string
{
return $this->tagsMode;
}
public function dateRange(): ?DateRange
{
return $this->dateRange;
}
public function apiKey(): ?ApiKey
{
return $this->apiKey;
}
}

View File

@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Persistence;
use Shlinkio\Shlink\Common\Util\DateRange;
use Shlinkio\Shlink\Core\Model\Ordering;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ShortUrlsListFiltering extends ShortUrlsCountFiltering
{
public function __construct(
private ?int $limit,
private ?int $offset,
private Ordering $orderBy,
?string $searchTerm = null,
array $tags = [],
?string $tagsMode = null,
?DateRange $dateRange = null,
?ApiKey $apiKey = null,
) {
parent::__construct($searchTerm, $tags, $tagsMode, $dateRange, $apiKey);
}
public static function fromLimitsAndParams(int $limit, int $offset, ShortUrlsParams $params, ?ApiKey $apiKey): self
{
return new self(
$limit,
$offset,
$params->orderBy(),
$params->searchTerm(),
$params->tags(),
$params->tagsMode(),
$params->dateRange(),
$apiKey,
);
}
public function offset(): ?int
{
return $this->offset;
}
public function limit(): ?int
{
return $this->limit;
}
public function orderBy(): Ordering
{
return $this->orderBy;
}
}

View File

@ -17,6 +17,8 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver; use Shlinkio\Shlink\Core\ShortUrl\Resolver\PersistenceShortUrlRelationResolver;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta; use Shlinkio\Shlink\Rest\ApiKey\Model\ApiKeyMeta;
@ -55,25 +57,32 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
$this->getEntityManager()->flush(); $this->getEntityManager()->flush();
self::assertSame($regularOne, $this->repo->findOneWithDomainFallback($regularOne->getShortCode()));
self::assertSame($regularOne, $this->repo->findOneWithDomainFallback( self::assertSame($regularOne, $this->repo->findOneWithDomainFallback(
$withDomainDuplicatingRegular->getShortCode(), ShortUrlIdentifier::fromShortCodeAndDomain($regularOne->getShortCode()),
));
self::assertSame($regularOne, $this->repo->findOneWithDomainFallback(
ShortUrlIdentifier::fromShortCodeAndDomain($withDomainDuplicatingRegular->getShortCode()),
)); ));
self::assertSame($withDomain, $this->repo->findOneWithDomainFallback( self::assertSame($withDomain, $this->repo->findOneWithDomainFallback(
$withDomain->getShortCode(), ShortUrlIdentifier::fromShortCodeAndDomain($withDomain->getShortCode(), 'example.com'),
'example.com',
)); ));
self::assertSame( self::assertSame(
$withDomainDuplicatingRegular, $withDomainDuplicatingRegular,
$this->repo->findOneWithDomainFallback($withDomainDuplicatingRegular->getShortCode(), 'doma.in'), $this->repo->findOneWithDomainFallback(
ShortUrlIdentifier::fromShortCodeAndDomain($withDomainDuplicatingRegular->getShortCode(), 'doma.in'),
),
); );
self::assertSame( self::assertSame($regularOne, $this->repo->findOneWithDomainFallback(ShortUrlIdentifier::fromShortCodeAndDomain(
$regularOne, $withDomainDuplicatingRegular->getShortCode(),
$this->repo->findOneWithDomainFallback($withDomainDuplicatingRegular->getShortCode(), 'other-domain.com'), 'other-domain.com',
); )));
self::assertNull($this->repo->findOneWithDomainFallback('invalid')); self::assertNull($this->repo->findOneWithDomainFallback(ShortUrlIdentifier::fromShortCodeAndDomain('invalid')));
self::assertNull($this->repo->findOneWithDomainFallback($withDomain->getShortCode())); self::assertNull($this->repo->findOneWithDomainFallback(
self::assertNull($this->repo->findOneWithDomainFallback($withDomain->getShortCode(), 'other-domain.com')); ShortUrlIdentifier::fromShortCodeAndDomain($withDomain->getShortCode()),
));
self::assertNull($this->repo->findOneWithDomainFallback(
ShortUrlIdentifier::fromShortCodeAndDomain($withDomain->getShortCode(), 'other-domain.com'),
));
} }
/** @test */ /** @test */
@ -85,7 +94,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
} }
$this->getEntityManager()->flush(); $this->getEntityManager()->flush();
self::assertEquals($count, $this->repo->countList()); self::assertEquals($count, $this->repo->countList(new ShortUrlsCountFiltering()));
} }
/** @test */ /** @test */
@ -112,44 +121,49 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
$this->getEntityManager()->flush(); $this->getEntityManager()->flush();
$result = $this->repo->findList(null, null, 'foo', ['bar']); $result = $this->repo->findList(
new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), 'foo', ['bar']),
);
self::assertCount(1, $result); self::assertCount(1, $result);
self::assertEquals(1, $this->repo->countList('foo', ['bar'])); self::assertEquals(1, $this->repo->countList(new ShortUrlsCountFiltering('foo', ['bar'])));
self::assertSame($foo, $result[0]); self::assertSame($foo, $result[0]);
$result = $this->repo->findList(); $result = $this->repo->findList(new ShortUrlsListFiltering(null, null, Ordering::emptyInstance()));
self::assertCount(3, $result); self::assertCount(3, $result);
$result = $this->repo->findList(2); $result = $this->repo->findList(new ShortUrlsListFiltering(2, null, Ordering::emptyInstance()));
self::assertCount(2, $result); self::assertCount(2, $result);
$result = $this->repo->findList(2, 1); $result = $this->repo->findList(new ShortUrlsListFiltering(2, 1, Ordering::emptyInstance()));
self::assertCount(2, $result); self::assertCount(2, $result);
self::assertCount(1, $this->repo->findList(2, 2)); self::assertCount(1, $this->repo->findList(new ShortUrlsListFiltering(2, 2, Ordering::emptyInstance())));
$result = $this->repo->findList(null, null, null, [], null, Ordering::fromTuple(['visits', 'DESC'])); $result = $this->repo->findList(
new ShortUrlsListFiltering(null, null, Ordering::fromTuple(['visits', 'DESC'])),
);
self::assertCount(3, $result); self::assertCount(3, $result);
self::assertSame($bar, $result[0]); self::assertSame($bar, $result[0]);
$result = $this->repo->findList(null, null, null, [], null, null, DateRange::withEndDate( $result = $this->repo->findList(
Chronos::now()->subDays(2), new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, [], null, DateRange::withEndDate(
));
self::assertCount(1, $result);
self::assertEquals(1, $this->repo->countList(null, [], null, DateRange::withEndDate(
Chronos::now()->subDays(2),
)));
self::assertSame($foo2, $result[0]);
self::assertCount(
2,
$this->repo->findList(null, null, null, [], null, null, DateRange::withStartDate(
Chronos::now()->subDays(2), Chronos::now()->subDays(2),
)), )),
); );
self::assertEquals(2, $this->repo->countList(null, [], null, DateRange::withStartDate( self::assertCount(1, $result);
self::assertEquals(1, $this->repo->countList(new ShortUrlsCountFiltering(null, [], null, DateRange::withEndDate(
Chronos::now()->subDays(2), Chronos::now()->subDays(2),
))); ))));
self::assertSame($foo2, $result[0]);
self::assertCount(2, $this->repo->findList(
new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, [], null, DateRange::withStartDate(
Chronos::now()->subDays(2),
)),
));
self::assertEquals(2, $this->repo->countList(
new ShortUrlsCountFiltering(null, [], null, DateRange::withStartDate(Chronos::now()->subDays(2))),
));
} }
/** @test */ /** @test */
@ -162,7 +176,9 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
$this->getEntityManager()->flush(); $this->getEntityManager()->flush();
$result = $this->repo->findList(null, null, null, [], null, Ordering::fromTuple(['longUrl', 'ASC'])); $result = $this->repo->findList(
new ShortUrlsListFiltering(null, null, Ordering::fromTuple(['longUrl', 'ASC'])),
);
self::assertCount(count($urls), $result); self::assertCount(count($urls), $result);
self::assertEquals('a', $result[0]->getLongUrl()); self::assertEquals('a', $result[0]->getLongUrl());
@ -202,38 +218,86 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
$this->getEntityManager()->flush(); $this->getEntityManager()->flush();
self::assertCount(5, $this->repo->findList(null, null, null, ['foo', 'bar']));
self::assertCount(5, $this->repo->findList(null, null, null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ANY));
self::assertCount(1, $this->repo->findList(null, null, null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ALL));
self::assertEquals(5, $this->repo->countList(null, ['foo', 'bar']));
self::assertEquals(5, $this->repo->countList(null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ANY));
self::assertEquals(1, $this->repo->countList(null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ALL));
self::assertCount(4, $this->repo->findList(null, null, null, ['bar', 'baz']));
self::assertCount(4, $this->repo->findList(null, null, null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ANY));
self::assertCount(2, $this->repo->findList(null, null, null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ALL));
self::assertEquals(4, $this->repo->countList(null, ['bar', 'baz']));
self::assertEquals(4, $this->repo->countList(null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ANY));
self::assertEquals(2, $this->repo->countList(null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ALL));
self::assertCount(5, $this->repo->findList(null, null, null, ['foo', 'bar', 'baz']));
self::assertCount(5, $this->repo->findList( self::assertCount(5, $this->repo->findList(
new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, ['foo', 'bar']),
));
self::assertCount(5, $this->repo->findList(new ShortUrlsListFiltering(
null, null,
null, null,
Ordering::emptyInstance(),
null,
['foo', 'bar'],
ShortUrlsParams::TAGS_MODE_ANY,
)));
self::assertCount(1, $this->repo->findList(new ShortUrlsListFiltering(
null,
null,
Ordering::emptyInstance(),
null,
['foo', 'bar'],
ShortUrlsParams::TAGS_MODE_ALL,
)));
self::assertEquals(5, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar'])));
self::assertEquals(5, $this->repo->countList(
new ShortUrlsCountFiltering(null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ANY),
));
self::assertEquals(1, $this->repo->countList(
new ShortUrlsCountFiltering(null, ['foo', 'bar'], ShortUrlsParams::TAGS_MODE_ALL),
));
self::assertCount(4, $this->repo->findList(
new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, ['bar', 'baz']),
));
self::assertCount(4, $this->repo->findList(new ShortUrlsListFiltering(
null,
null,
Ordering::emptyInstance(),
null,
['bar', 'baz'],
ShortUrlsParams::TAGS_MODE_ANY,
)));
self::assertCount(2, $this->repo->findList(new ShortUrlsListFiltering(
null,
null,
Ordering::emptyInstance(),
null,
['bar', 'baz'],
ShortUrlsParams::TAGS_MODE_ALL,
)));
self::assertEquals(4, $this->repo->countList(new ShortUrlsCountFiltering(null, ['bar', 'baz'])));
self::assertEquals(4, $this->repo->countList(
new ShortUrlsCountFiltering(null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ANY),
));
self::assertEquals(2, $this->repo->countList(
new ShortUrlsCountFiltering(null, ['bar', 'baz'], ShortUrlsParams::TAGS_MODE_ALL),
));
self::assertCount(5, $this->repo->findList(
new ShortUrlsListFiltering(null, null, Ordering::emptyInstance(), null, ['foo', 'bar', 'baz']),
));
self::assertCount(5, $this->repo->findList(new ShortUrlsListFiltering(
null,
null,
Ordering::emptyInstance(),
null, null,
['foo', 'bar', 'baz'], ['foo', 'bar', 'baz'],
ShortUrlsParams::TAGS_MODE_ANY, ShortUrlsParams::TAGS_MODE_ANY,
)); )));
self::assertCount(0, $this->repo->findList( self::assertCount(0, $this->repo->findList(new ShortUrlsListFiltering(
null, null,
null, null,
Ordering::emptyInstance(),
null, null,
['foo', 'bar', 'baz'], ['foo', 'bar', 'baz'],
ShortUrlsParams::TAGS_MODE_ALL, ShortUrlsParams::TAGS_MODE_ALL,
)));
self::assertEquals(5, $this->repo->countList(new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'])));
self::assertEquals(5, $this->repo->countList(
new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'], ShortUrlsParams::TAGS_MODE_ANY),
));
self::assertEquals(0, $this->repo->countList(
new ShortUrlsCountFiltering(null, ['foo', 'bar', 'baz'], ShortUrlsParams::TAGS_MODE_ALL),
)); ));
self::assertEquals(5, $this->repo->countList(null, ['foo', 'bar', 'baz']));
self::assertEquals(5, $this->repo->countList(null, ['foo', 'bar', 'baz'], ShortUrlsParams::TAGS_MODE_ANY));
self::assertEquals(0, $this->repo->countList(null, ['foo', 'bar', 'baz'], ShortUrlsParams::TAGS_MODE_ALL));
} }
/** @test */ /** @test */

View File

@ -86,7 +86,9 @@ class ShortUrlResolverTest extends TestCase
$shortCode = $shortUrl->getShortCode(); $shortCode = $shortUrl->getShortCode();
$repo = $this->prophesize(ShortUrlRepositoryInterface::class); $repo = $this->prophesize(ShortUrlRepositoryInterface::class);
$findOneByShortCode = $repo->findOneWithDomainFallback($shortCode, null)->willReturn($shortUrl); $findOneByShortCode = $repo->findOneWithDomainFallback(
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
)->willReturn($shortUrl);
$getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
$result = $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode)); $result = $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode));
@ -105,7 +107,9 @@ class ShortUrlResolverTest extends TestCase
$shortCode = $shortUrl->getShortCode(); $shortCode = $shortUrl->getShortCode();
$repo = $this->prophesize(ShortUrlRepositoryInterface::class); $repo = $this->prophesize(ShortUrlRepositoryInterface::class);
$findOneByShortCode = $repo->findOneWithDomainFallback($shortCode, null)->willReturn($shortUrl); $findOneByShortCode = $repo->findOneWithDomainFallback(
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
)->willReturn($shortUrl);
$getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
$this->expectException(ShortUrlNotFoundException::class); $this->expectException(ShortUrlNotFoundException::class);

View File

@ -2,15 +2,17 @@
declare(strict_types=1); declare(strict_types=1);
namespace ShlinkioTest\Shlink\Core\Paginator\Adapter; namespace ShlinkioTest\Shlink\Core\ShortUrl\Paginator\Adapter;
use Cake\Chronos\Chronos; use Cake\Chronos\Chronos;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\PhpUnit\ProphecyTrait;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams; use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
use Shlinkio\Shlink\Core\ShortUrl\Paginator\Adapter\ShortUrlRepositoryAdapter;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsCountFiltering;
use Shlinkio\Shlink\Core\ShortUrl\Persistence\ShortUrlsListFiltering;
use Shlinkio\Shlink\Rest\Entity\ApiKey; use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ShortUrlRepositoryAdapterTest extends TestCase class ShortUrlRepositoryAdapterTest extends TestCase
@ -46,8 +48,9 @@ class ShortUrlRepositoryAdapterTest extends TestCase
$orderBy = $params->orderBy(); $orderBy = $params->orderBy();
$dateRange = $params->dateRange(); $dateRange = $params->dateRange();
$this->repo->findList(10, 5, $searchTerm, $tags, ShortUrlsParams::TAGS_MODE_ANY, $orderBy, $dateRange, null) $this->repo->findList(
->shouldBeCalledOnce(); new ShortUrlsListFiltering(10, 5, $orderBy, $searchTerm, $tags, ShortUrlsParams::TAGS_MODE_ANY, $dateRange),
)->shouldBeCalledOnce();
$adapter->getSlice(5, 10); $adapter->getSlice(5, 10);
} }
@ -71,8 +74,9 @@ class ShortUrlRepositoryAdapterTest extends TestCase
$adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), $params, $apiKey); $adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), $params, $apiKey);
$dateRange = $params->dateRange(); $dateRange = $params->dateRange();
$this->repo->countList($searchTerm, $tags, ShortUrlsParams::TAGS_MODE_ANY, $dateRange, $apiKey->spec()) $this->repo->countList(
->shouldBeCalledOnce(); new ShortUrlsCountFiltering($searchTerm, $tags, ShortUrlsParams::TAGS_MODE_ANY, $dateRange, $apiKey),
)->shouldBeCalledOnce();
$adapter->getNbResults(); $adapter->getNbResults();
} }