Passed
Pull Request — master (#343)
by Alejandro
05:42
created

whenCustomSlugIsProvidedItIsUsed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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