Completed
Push — develop ( 0686ac...7ecc3a )
by Alejandro
18s queued 11s
created

shortCodeIsRegeneratedIfAlreadyInUse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
c 0
b 0
f 0
dl 0
loc 23
rs 9.7666
cc 1
nc 1
nop 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A UrlShortenerTest::exceptionIsThrownWhenNonUniqueSlugIsProvided() 0 11 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ShlinkioTest\Shlink\Core\Service;
6
7
use Cake\Chronos\Chronos;
8
use Doctrine\Common\Collections\ArrayCollection;
9
use Doctrine\DBAL\Connection;
10
use Doctrine\ORM\EntityManagerInterface;
11
use Doctrine\ORM\ORMException;
12
use PHPUnit\Framework\TestCase;
13
use Prophecy\Argument;
14
use Prophecy\Prophecy\ObjectProphecy;
15
use Shlinkio\Shlink\Core\Domain\Resolver\SimpleDomainResolver;
16
use Shlinkio\Shlink\Core\Entity\ShortUrl;
17
use Shlinkio\Shlink\Core\Entity\Tag;
18
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
19
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
20
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
21
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface;
22
use Shlinkio\Shlink\Core\Service\UrlShortener;
23
use Shlinkio\Shlink\Core\Util\UrlValidatorInterface;
24
25
class UrlShortenerTest extends TestCase
26
{
27
    private UrlShortener $urlShortener;
28
    private ObjectProphecy $em;
29
    private ObjectProphecy $urlValidator;
30
    private ObjectProphecy $shortCodeHelper;
31
32
    public function setUp(): void
33
    {
34
        $this->urlValidator = $this->prophesize(UrlValidatorInterface::class);
35
        $this->urlValidator->validateUrl('http://foobar.com/12345/hello?foo=bar', null)->will(
36
            function (): void {
37
            },
38
        );
39
40
        $this->em = $this->prophesize(EntityManagerInterface::class);
41
        $conn = $this->prophesize(Connection::class);
42
        $conn->isTransactionActive()->willReturn(false);
43
        $this->em->getConnection()->willReturn($conn->reveal());
44
        $this->em->flush()->willReturn(null);
45
        $this->em->commit()->willReturn(null);
46
        $this->em->beginTransaction()->willReturn(null);
47
        $this->em->persist(Argument::any())->will(function ($arguments): void {
48
            /** @var ShortUrl $shortUrl */
49
            [$shortUrl] = $arguments;
50
            $shortUrl->setId('10');
51
        });
52
        $repo = $this->prophesize(ShortUrlRepository::class);
53
        $repo->shortCodeIsInUse(Argument::cetera())->willReturn(false);
54
        $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
55
56
        $this->shortCodeHelper = $this->prophesize(ShortCodeHelperInterface::class);
57
        $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true);
58
59
        $this->urlShortener = new UrlShortener(
60
            $this->urlValidator->reveal(),
61
            $this->em->reveal(),
62
            new SimpleDomainResolver(),
63
            $this->shortCodeHelper->reveal(),
64
        );
65
    }
66
67
    /** @test */
68
    public function urlIsProperlyShortened(): void
69
    {
70
        $shortUrl = $this->urlShortener->urlToShortCode(
71
            'http://foobar.com/12345/hello?foo=bar',
72
            [],
73
            ShortUrlMeta::createEmpty(),
74
        );
75
76
        self::assertEquals('http://foobar.com/12345/hello?foo=bar', $shortUrl->getLongUrl());
77
    }
78
79
    /** @test */
80
    public function exceptionIsThrownWhenNonUniqueSlugIsProvided(): void
81
    {
82
        $ensureUniqueness = $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(false);
83
84
        $ensureUniqueness->shouldBeCalledOnce();
85
        $this->expectException(NonUniqueSlugException::class);
86
87
        $this->urlShortener->urlToShortCode(
88
            'http://foobar.com/12345/hello?foo=bar',
89
            [],
90
            ShortUrlMeta::fromRawData(['customSlug' => 'custom-slug']),
91
        );
92
    }
93
94
    /** @test */
95
    public function transactionIsRolledBackAndExceptionRethrownWhenExceptionIsThrown(): void
96
    {
97
        $conn = $this->prophesize(Connection::class);
98
        $conn->isTransactionActive()->willReturn(true);
99
        $this->em->getConnection()->willReturn($conn->reveal());
100
        $this->em->rollback()->shouldBeCalledOnce();
101
        $this->em->close()->shouldBeCalledOnce();
102
103
        $this->em->flush()->willThrow(new ORMException());
104
105
        $this->expectException(ORMException::class);
106
        $this->urlShortener->urlToShortCode(
107
            'http://foobar.com/12345/hello?foo=bar',
108
            [],
109
            ShortUrlMeta::createEmpty(),
110
        );
111
    }
112
113
    /**
114
     * @test
115
     * @dataProvider provideExistingShortUrls
116
     */
117
    public function existingShortUrlIsReturnedWhenRequested(
118
        string $url,
119
        array $tags,
120
        ShortUrlMeta $meta,
121
        ShortUrl $expected
122
    ): void {
123
        $repo = $this->prophesize(ShortUrlRepository::class);
124
        $findExisting = $repo->findOneMatching(Argument::cetera())->willReturn($expected);
125
        $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
126
127
        $result = $this->urlShortener->urlToShortCode($url, $tags, $meta);
128
129
        $findExisting->shouldHaveBeenCalledOnce();
130
        $getRepo->shouldHaveBeenCalledOnce();
131
        $this->em->persist(Argument::cetera())->shouldNotHaveBeenCalled();
132
        $this->urlValidator->validateUrl(Argument::cetera())->shouldNotHaveBeenCalled();
133
        self::assertSame($expected, $result);
134
    }
135
136
    public function provideExistingShortUrls(): iterable
137
    {
138
        $url = 'http://foo.com';
139
140
        yield [$url, [], ShortUrlMeta::fromRawData(['findIfExists' => true]), new ShortUrl($url)];
141
        yield [$url, [], ShortUrlMeta::fromRawData(
142
            ['findIfExists' => true, 'customSlug' => 'foo'],
143
        ), new ShortUrl($url)];
144
        yield [
145
            $url,
146
            ['foo', 'bar'],
147
            ShortUrlMeta::fromRawData(['findIfExists' => true]),
148
            (new ShortUrl($url))->setTags(new ArrayCollection([new Tag('bar'), new Tag('foo')])),
149
        ];
150
        yield [
151
            $url,
152
            [],
153
            ShortUrlMeta::fromRawData(['findIfExists' => true, 'maxVisits' => 3]),
154
            new ShortUrl($url, ShortUrlMeta::fromRawData(['maxVisits' => 3])),
155
        ];
156
        yield [
157
            $url,
158
            [],
159
            ShortUrlMeta::fromRawData(['findIfExists' => true, 'validSince' => Chronos::parse('2017-01-01')]),
160
            new ShortUrl($url, ShortUrlMeta::fromRawData(['validSince' => Chronos::parse('2017-01-01')])),
161
        ];
162
        yield [
163
            $url,
164
            [],
165
            ShortUrlMeta::fromRawData(['findIfExists' => true, 'validUntil' => Chronos::parse('2017-01-01')]),
166
            new ShortUrl($url, ShortUrlMeta::fromRawData(['validUntil' => Chronos::parse('2017-01-01')])),
167
        ];
168
        yield [
169
            $url,
170
            [],
171
            ShortUrlMeta::fromRawData(['findIfExists' => true, 'domain' => 'example.com']),
172
            new ShortUrl($url, ShortUrlMeta::fromRawData(['domain' => 'example.com'])),
173
        ];
174
        yield [
175
            $url,
176
            ['baz', 'foo', 'bar'],
177
            ShortUrlMeta::fromRawData([
178
                'findIfExists' => true,
179
                'validUntil' => Chronos::parse('2017-01-01'),
180
                'maxVisits' => 4,
181
            ]),
182
            (new ShortUrl($url, ShortUrlMeta::fromRawData([
183
                'validUntil' => Chronos::parse('2017-01-01'),
184
                'maxVisits' => 4,
185
            ])))->setTags(new ArrayCollection([new Tag('foo'), new Tag('bar'), new Tag('baz')])),
186
        ];
187
    }
188
}
189