Completed
Push — master ( 5c5dde...08bd4f )
by Alejandro
17s queued 10s
created

UrlShortenerTest::provideExistingShortUrls()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 44
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 44
rs 9.392
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace ShlinkioTest\Shlink\Core\Service;
5
6
use Cake\Chronos\Chronos;
7
use Doctrine\Common\Collections\ArrayCollection;
8
use Doctrine\DBAL\Connection;
9
use Doctrine\ORM\EntityManagerInterface;
10
use Doctrine\ORM\ORMException;
11
use GuzzleHttp\ClientInterface;
12
use GuzzleHttp\Exception\ClientException;
13
use GuzzleHttp\Psr7\Request;
14
use PHPUnit\Framework\TestCase;
15
use Prophecy\Argument;
16
use Prophecy\Prophecy\ObjectProphecy;
17
use Shlinkio\Shlink\Core\Entity\ShortUrl;
18
use Shlinkio\Shlink\Core\Entity\Tag;
19
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
20
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
21
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
22
use Shlinkio\Shlink\Core\Exception\RuntimeException;
23
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
24
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
25
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
26
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
27
use Shlinkio\Shlink\Core\Service\UrlShortener;
28
use Zend\Diactoros\Uri;
29
30
class UrlShortenerTest extends TestCase
31
{
32
    /** @var UrlShortener */
33
    private $urlShortener;
34
    /** @var ObjectProphecy */
35
    private $em;
36
    /** @var ObjectProphecy */
37
    private $httpClient;
38
39
    public function setUp(): void
40
    {
41
        $this->httpClient = $this->prophesize(ClientInterface::class);
42
43
        $this->em = $this->prophesize(EntityManagerInterface::class);
44
        $conn = $this->prophesize(Connection::class);
45
        $conn->isTransactionActive()->willReturn(false);
46
        $this->em->getConnection()->willReturn($conn->reveal());
47
        $this->em->flush()->willReturn(null);
48
        $this->em->commit()->willReturn(null);
49
        $this->em->beginTransaction()->willReturn(null);
50
        $this->em->persist(Argument::any())->will(function ($arguments) {
51
            /** @var ShortUrl $shortUrl */
52
            $shortUrl = $arguments[0];
53
            $shortUrl->setId('10');
54
        });
55
        $repo = $this->prophesize(ShortUrlRepository::class);
56
        $repo->count(Argument::any())->willReturn(0);
57
        $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
58
59
        $this->setUrlShortener(false);
60
    }
61
62
    private function setUrlShortener(bool $urlValidationEnabled): void
63
    {
64
        $this->urlShortener = new UrlShortener(
65
            $this->httpClient->reveal(),
66
            $this->em->reveal(),
67
            new UrlShortenerOptions(['validate_url' => $urlValidationEnabled])
68
        );
69
    }
70
71
    /** @test */
72
    public function urlIsProperlyShortened(): void
73
    {
74
        // 10 -> 0Q1Y
75
        $shortUrl = $this->urlShortener->urlToShortCode(
76
            new Uri('http://foobar.com/12345/hello?foo=bar'),
77
            [],
78
            ShortUrlMeta::createEmpty()
79
        );
80
        $this->assertEquals('0Q1Y', $shortUrl->getShortCode());
81
    }
82
83
    /** @test */
84
    public function exceptionIsThrownWhenOrmThrowsException(): void
85
    {
86
        $conn = $this->prophesize(Connection::class);
87
        $conn->isTransactionActive()->willReturn(true);
88
        $this->em->getConnection()->willReturn($conn->reveal());
89
        $this->em->rollback()->shouldBeCalledOnce();
90
        $this->em->close()->shouldBeCalledOnce();
91
92
        $this->em->flush()->willThrow(new ORMException());
93
94
        $this->expectException(RuntimeException::class);
95
        $this->urlShortener->urlToShortCode(
96
            new Uri('http://foobar.com/12345/hello?foo=bar'),
97
            [],
98
            ShortUrlMeta::createEmpty()
99
        );
100
    }
101
102
    /** @test */
103
    public function exceptionIsThrownWhenUrlDoesNotExist(): void
104
    {
105
        $this->setUrlShortener(true);
106
107
        $this->httpClient->request(Argument::cetera())->willThrow(
108
            new ClientException('', $this->prophesize(Request::class)->reveal())
109
        );
110
111
        $this->expectException(InvalidUrlException::class);
112
        $this->urlShortener->urlToShortCode(
113
            new Uri('http://foobar.com/12345/hello?foo=bar'),
114
            [],
115
            ShortUrlMeta::createEmpty()
116
        );
117
    }
118
119
    /** @test */
120
    public function exceptionIsThrownWhenNonUniqueSlugIsProvided(): void
121
    {
122
        $repo = $this->prophesize(ShortUrlRepository::class);
123
        $countBySlug = $repo->count(['shortCode' => 'custom-slug'])->willReturn(1);
124
        $repo->findOneBy(Argument::cetera())->willReturn(null);
125
        $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
126
127
        $countBySlug->shouldBeCalledOnce();
128
        $getRepo->shouldBeCalled();
129
        $this->expectException(NonUniqueSlugException::class);
130
131
        $this->urlShortener->urlToShortCode(
132
            new Uri('http://foobar.com/12345/hello?foo=bar'),
133
            [],
134
            ShortUrlMeta::createFromRawData(['customSlug' => 'custom-slug'])
135
        );
136
    }
137
138
    /**
139
     * @test
140
     * @dataProvider provideExistingShortUrls
141
     */
142
    public function existingShortUrlIsReturnedWhenRequested(
143
        string $url,
144
        array $tags,
145
        ShortUrlMeta $meta,
146
        ?ShortUrl $expected
147
    ): void {
148
        $repo = $this->prophesize(ShortUrlRepository::class);
149
        $findExisting = $repo->findOneBy(Argument::any())->willReturn($expected);
150
        $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
151
152
        $result = $this->urlShortener->urlToShortCode(new Uri($url), $tags, $meta);
153
154
        $this->assertSame($expected, $result);
155
        $findExisting->shouldHaveBeenCalledOnce();
156
        $getRepo->shouldHaveBeenCalledOnce();
157
    }
158
159
    public function provideExistingShortUrls(): iterable
160
    {
161
        $url = 'http://foo.com';
162
163
        yield [$url, [], ShortUrlMeta::createFromRawData(['findIfExists' => true]), new ShortUrl($url)];
164
        yield [$url, [], ShortUrlMeta::createFromRawData(
165
            ['findIfExists' => true, 'customSlug' => 'foo']
166
        ), new ShortUrl($url)];
167
        yield [
168
            $url,
169
            ['foo', 'bar'],
170
            ShortUrlMeta::createFromRawData(['findIfExists' => true]),
171
            (new ShortUrl($url))->setTags(new ArrayCollection([new Tag('bar'), new Tag('foo')])),
172
        ];
173
        yield [
174
            $url,
175
            [],
176
            ShortUrlMeta::createFromRawData(['findIfExists' => true, 'maxVisits' => 3]),
177
            new ShortUrl($url, ShortUrlMeta::createFromRawData(['maxVisits' => 3])),
178
        ];
179
        yield [
180
            $url,
181
            [],
182
            ShortUrlMeta::createFromRawData(['findIfExists' => true, 'validSince' => Chronos::parse('2017-01-01')]),
183
            new ShortUrl($url, ShortUrlMeta::createFromRawData(['validSince' => Chronos::parse('2017-01-01')])),
184
        ];
185
        yield [
186
            $url,
187
            [],
188
            ShortUrlMeta::createFromRawData(['findIfExists' => true, 'validUntil' => Chronos::parse('2017-01-01')]),
189
            new ShortUrl($url, ShortUrlMeta::createFromRawData(['validUntil' => Chronos::parse('2017-01-01')])),
190
        ];
191
        yield [
192
            $url,
193
            ['baz', 'foo', 'bar'],
194
            ShortUrlMeta::createFromRawData([
195
                'findIfExists' => true,
196
                'validUntil' => Chronos::parse('2017-01-01'),
197
                'maxVisits' => 4,
198
            ]),
199
            (new ShortUrl($url, ShortUrlMeta::createFromRawData([
200
                'validUntil' => Chronos::parse('2017-01-01'),
201
                'maxVisits' => 4,
202
            ])))->setTags(new ArrayCollection([new Tag('foo'), new Tag('bar'), new Tag('baz')])),
203
        ];
204
    }
205
206
    /** @test */
207
    public function shortCodeIsProperlyParsed(): void
208
    {
209
        $shortCode = '12C1c';
210
        $shortUrl = new ShortUrl('expected_url');
211
        $shortUrl->setShortCode($shortCode);
212
213
        $repo = $this->prophesize(ShortUrlRepositoryInterface::class);
214
        $repo->findOneByShortCode($shortCode)->willReturn($shortUrl);
215
        $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
216
217
        $url = $this->urlShortener->shortCodeToUrl($shortCode);
218
        $this->assertSame($shortUrl, $url);
219
    }
220
221
    /** @test */
222
    public function invalidCharSetThrowsException(): void
223
    {
224
        $this->expectException(InvalidShortCodeException::class);
225
        $this->urlShortener->shortCodeToUrl('&/(');
226
    }
227
}
228