Completed
Push — master ( ccb9d5...d7e68d )
by Alejandro
09:04
created

exceptionIsThrownWhenNonUniqueSlugIsProvided()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 15
nc 1
nop 0
dl 0
loc 25
rs 8.8571
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace ShlinkioTest\Shlink\Core\Service;
5
6
use Cocur\Slugify\SlugifyInterface;
7
use Doctrine\Common\Cache\ArrayCache;
8
use Doctrine\Common\Cache\Cache;
9
use Doctrine\Common\Persistence\ObjectRepository;
10
use Doctrine\DBAL\Connection;
11
use Doctrine\ORM\EntityManagerInterface;
12
use Doctrine\ORM\ORMException;
13
use GuzzleHttp\ClientInterface;
14
use GuzzleHttp\Exception\ClientException;
15
use GuzzleHttp\Psr7\Request;
16
use PHPUnit\Framework\TestCase;
17
use Prophecy\Argument;
18
use Prophecy\Prophecy\MethodProphecy;
19
use Prophecy\Prophecy\ObjectProphecy;
20
use Shlinkio\Shlink\Core\Entity\ShortUrl;
21
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
22
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
23
use Shlinkio\Shlink\Core\Service\UrlShortener;
24
use Zend\Diactoros\Uri;
25
26
class UrlShortenerTest extends TestCase
27
{
28
    /**
29
     * @var UrlShortener
30
     */
31
    protected $urlShortener;
32
    /**
33
     * @var ObjectProphecy
34
     */
35
    protected $em;
36
    /**
37
     * @var ObjectProphecy
38
     */
39
    protected $httpClient;
40
    /**
41
     * @var Cache
42
     */
43
    protected $cache;
44
    /**
45
     * @var ObjectProphecy
46
     */
47
    protected $slugger;
48
49
    public function setUp()
50
    {
51
        $this->httpClient = $this->prophesize(ClientInterface::class);
52
53
        $this->em = $this->prophesize(EntityManagerInterface::class);
54
        $conn = $this->prophesize(Connection::class);
55
        $conn->isTransactionActive()->willReturn(false);
56
        $this->em->getConnection()->willReturn($conn->reveal());
57
        $this->em->flush()->willReturn(null);
58
        $this->em->commit()->willReturn(null);
59
        $this->em->beginTransaction()->willReturn(null);
60
        $this->em->persist(Argument::any())->will(function ($arguments) {
61
            /** @var ShortUrl $shortUrl */
62
            $shortUrl = $arguments[0];
63
            $shortUrl->setId(10);
64
        });
65
        $repo = $this->prophesize(ObjectRepository::class);
66
        $repo->findOneBy(Argument::any())->willReturn(null);
67
        $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
68
69
        $this->cache = new ArrayCache();
70
        $this->slugger = $this->prophesize(SlugifyInterface::class);
71
72
        $this->setUrlShortener(false);
73
    }
74
75
    /**
76
     * @param bool $urlValidationEnabled
77
     */
78
    public function setUrlShortener($urlValidationEnabled)
79
    {
80
        $this->urlShortener = new UrlShortener(
81
            $this->httpClient->reveal(),
82
            $this->em->reveal(),
83
            $this->cache,
84
            $urlValidationEnabled,
85
            UrlShortener::DEFAULT_CHARS,
86
            $this->slugger->reveal()
87
        );
88
    }
89
90
    /**
91
     * @test
92
     */
93
    public function urlIsProperlyShortened()
94
    {
95
        // 10 -> 12C1c
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
96
        $shortCode = $this->urlShortener->urlToShortCode(new Uri('http://foobar.com/12345/hello?foo=bar'));
97
        $this->assertEquals('12C1c', $shortCode);
98
    }
99
100
    /**
101
     * @test
102
     * @expectedException \Shlinkio\Shlink\Common\Exception\RuntimeException
103
     */
104
    public function exceptionIsThrownWhenOrmThrowsException()
105
    {
106
        $conn = $this->prophesize(Connection::class);
107
        $conn->isTransactionActive()->willReturn(true);
108
        $this->em->getConnection()->willReturn($conn->reveal());
109
        $this->em->rollback()->shouldBeCalledTimes(1);
110
        $this->em->close()->shouldBeCalledTimes(1);
111
112
        $this->em->flush()->willThrow(new ORMException());
113
        $this->urlShortener->urlToShortCode(new Uri('http://foobar.com/12345/hello?foo=bar'));
114
    }
115
116
    /**
117
     * @test
118
     * @expectedException \Shlinkio\Shlink\Core\Exception\InvalidUrlException
119
     */
120
    public function exceptionIsThrownWhenUrlDoesNotExist()
121
    {
122
        $this->setUrlShortener(true);
123
124
        $this->httpClient->request(Argument::cetera())->willThrow(
125
            new ClientException('', $this->prophesize(Request::class)->reveal())
126
        );
127
        $this->urlShortener->urlToShortCode(new Uri('http://foobar.com/12345/hello?foo=bar'));
128
    }
129
130
    /**
131
     * @test
132
     */
133
    public function whenShortUrlExistsItsShortcodeIsReturned()
134
    {
135
        $shortUrl = new ShortUrl();
136
        $shortUrl->setShortCode('expected_shortcode');
137
        $repo = $this->prophesize(ObjectRepository::class);
138
        $repo->findOneBy(Argument::any())->willReturn($shortUrl);
139
        $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
140
141
        $shortCode = $this->urlShortener->urlToShortCode(new Uri('http://foobar.com/12345/hello?foo=bar'));
142
        $this->assertEquals($shortUrl->getShortCode(), $shortCode);
143
    }
144
145
    /**
146
     * @test
147
     */
148
    public function whenCustomSlugIsProvidedItIsUsed()
149
    {
150
        /** @var MethodProphecy $slugify */
151
        $slugify = $this->slugger->slugify('custom-slug')->willReturnArgument();
152
153
        $this->urlShortener->urlToShortCode(
154
            new Uri('http://foobar.com/12345/hello?foo=bar'),
155
            [],
156
            null,
157
            null,
158
            'custom-slug'
159
        );
160
161
        $slugify->shouldHaveBeenCalledTimes(1);
162
    }
163
164
    /**
165
     * @test
166
     */
167
    public function exceptionIsThrownWhenNonUniqueSlugIsProvided()
168
    {
169
        /** @var MethodProphecy $slugify */
170
        $slugify = $this->slugger->slugify('custom-slug')->willReturnArgument();
171
172
        $repo = $this->prophesize(ShortUrlRepositoryInterface::class);
173
        /** @var MethodProphecy $findBySlug */
174
        $findBySlug = $repo->findOneBy(['shortCode' => 'custom-slug'])->willReturn(new ShortUrl());
175
        $repo->findOneBy(Argument::cetera())->willReturn(null);
176
        /** @var MethodProphecy $getRepo */
177
        $getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
178
179
        $slugify->shouldBeCalledTimes(1);
180
        $findBySlug->shouldBeCalledTimes(1);
181
        $getRepo->shouldBeCalled();
182
        $this->expectException(NonUniqueSlugException::class);
183
184
        $this->urlShortener->urlToShortCode(
185
            new Uri('http://foobar.com/12345/hello?foo=bar'),
186
            [],
187
            null,
188
            null,
189
            'custom-slug'
190
        );
191
    }
192
193
    /**
194
     * @test
195
     */
196
    public function shortCodeIsProperlyParsed()
197
    {
198
        // 12C1c -> 10
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
199
        $shortCode = '12C1c';
200
        $shortUrl = new ShortUrl();
201
        $shortUrl->setShortCode($shortCode)
202
                 ->setOriginalUrl('expected_url');
203
204
        $repo = $this->prophesize(ShortUrlRepositoryInterface::class);
205
        $repo->findOneByShortCode($shortCode)->willReturn($shortUrl);
206
        $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
207
208
        $this->assertFalse($this->cache->contains($shortCode . '_longUrl'));
209
        $url = $this->urlShortener->shortCodeToUrl($shortCode);
210
        $this->assertEquals($shortUrl->getOriginalUrl(), $url);
211
        $this->assertTrue($this->cache->contains($shortCode . '_longUrl'));
212
    }
213
214
    /**
215
     * @test
216
     * @expectedException \Shlinkio\Shlink\Core\Exception\InvalidShortCodeException
217
     */
218
    public function invalidCharSetThrowsException()
219
    {
220
        $this->urlShortener->shortCodeToUrl('&/(');
221
    }
222
223
    /**
224
     * @test
225
     */
226
    public function cachedShortCodeDoesNotHitDatabase()
227
    {
228
        $shortCode = '12C1c';
229
        $expectedUrl = 'expected_url';
230
        $this->cache->save($shortCode . '_longUrl', $expectedUrl);
231
        $this->em->getRepository(ShortUrl::class)->willReturn(null)->shouldBeCalledTimes(0);
232
233
        $url = $this->urlShortener->shortCodeToUrl($shortCode);
234
        $this->assertEquals($expectedUrl, $url);
235
    }
236
}
237