Completed
Push — develop ( 8d438a...1f78f5 )
by Alejandro
16s queued 12s
created

ShortUrlRepositoryTest::setUp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ShlinkioTest\Shlink\Core\Repository;
6
7
use Cake\Chronos\Chronos;
8
use Doctrine\Common\Collections\ArrayCollection;
9
use ReflectionObject;
10
use Shlinkio\Shlink\Common\Util\DateRange;
11
use Shlinkio\Shlink\Core\Entity\Domain;
12
use Shlinkio\Shlink\Core\Entity\ShortUrl;
13
use Shlinkio\Shlink\Core\Entity\Tag;
14
use Shlinkio\Shlink\Core\Entity\Visit;
15
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
16
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
17
use Shlinkio\Shlink\Core\Model\Visitor;
18
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
19
use Shlinkio\Shlink\Core\Util\TagManagerTrait;
20
use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase;
21
22
use function count;
23
24
class ShortUrlRepositoryTest extends DatabaseTestCase
25
{
26
    use TagManagerTrait;
27
28
    protected const ENTITIES_TO_EMPTY = [
29
        Tag::class,
30
        Visit::class,
31
        ShortUrl::class,
32
        Domain::class,
33
    ];
34
35
    private ShortUrlRepository $repo;
36
37
    public function setUp(): void
38
    {
39
        $this->repo = $this->getEntityManager()->getRepository(ShortUrl::class);
40
    }
41
42
    /** @test */
43
    public function findOneWithDomainFallbackReturnsProperData(): void
44
    {
45
        $regularOne = new ShortUrl('foo', ShortUrlMeta::fromRawData(['customSlug' => 'foo']));
46
        $this->getEntityManager()->persist($regularOne);
47
48
        $withDomain = new ShortUrl('foo', ShortUrlMeta::fromRawData(
49
            ['domain' => 'example.com', 'customSlug' => 'domain-short-code'],
50
        ));
51
        $this->getEntityManager()->persist($withDomain);
52
53
        $withDomainDuplicatingRegular = new ShortUrl('foo_with_domain', ShortUrlMeta::fromRawData(
54
            ['domain' => 'doma.in', 'customSlug' => 'foo'],
55
        ));
56
        $this->getEntityManager()->persist($withDomainDuplicatingRegular);
57
58
        $this->getEntityManager()->flush();
59
60
        $this->assertSame($regularOne, $this->repo->findOneWithDomainFallback($regularOne->getShortCode()));
61
        $this->assertSame($regularOne, $this->repo->findOneWithDomainFallback(
62
            $withDomainDuplicatingRegular->getShortCode(),
63
        ));
64
        $this->assertSame($withDomain, $this->repo->findOneWithDomainFallback(
65
            $withDomain->getShortCode(),
66
            'example.com',
67
        ));
68
        $this->assertSame(
69
            $withDomainDuplicatingRegular,
70
            $this->repo->findOneWithDomainFallback($withDomainDuplicatingRegular->getShortCode(), 'doma.in'),
71
        );
72
        $this->assertSame(
73
            $regularOne,
74
            $this->repo->findOneWithDomainFallback($withDomainDuplicatingRegular->getShortCode(), 'other-domain.com'),
75
        );
76
        $this->assertNull($this->repo->findOneWithDomainFallback('invalid'));
77
        $this->assertNull($this->repo->findOneWithDomainFallback($withDomain->getShortCode()));
78
        $this->assertNull($this->repo->findOneWithDomainFallback($withDomain->getShortCode(), 'other-domain.com'));
79
    }
80
81
    /** @test */
82
    public function countListReturnsProperNumberOfResults(): void
83
    {
84
        $count = 5;
85
        for ($i = 0; $i < $count; $i++) {
86
            $this->getEntityManager()->persist(new ShortUrl((string) $i));
87
        }
88
        $this->getEntityManager()->flush();
89
90
        $this->assertEquals($count, $this->repo->countList());
91
    }
92
93
    /** @test */
94
    public function findListProperlyFiltersResult(): void
95
    {
96
        $tag = new Tag('bar');
97
        $this->getEntityManager()->persist($tag);
98
99
        $foo = new ShortUrl('foo');
100
        $foo->setTags(new ArrayCollection([$tag]));
101
        $this->getEntityManager()->persist($foo);
102
103
        $bar = new ShortUrl('bar');
104
        $visit = new Visit($bar, Visitor::emptyInstance());
105
        $this->getEntityManager()->persist($visit);
106
        $bar->setVisits(new ArrayCollection([$visit]));
107
        $this->getEntityManager()->persist($bar);
108
109
        $foo2 = new ShortUrl('foo_2');
110
        $ref = new ReflectionObject($foo2);
111
        $dateProp = $ref->getProperty('dateCreated');
112
        $dateProp->setAccessible(true);
113
        $dateProp->setValue($foo2, Chronos::now()->subDays(5));
114
        $this->getEntityManager()->persist($foo2);
115
116
        $this->getEntityManager()->flush();
117
118
        $result = $this->repo->findList(null, null, 'foo', ['bar']);
119
        $this->assertCount(1, $result);
120
        $this->assertEquals(1, $this->repo->countList('foo', ['bar']));
121
        $this->assertSame($foo, $result[0]);
122
123
        $result = $this->repo->findList();
124
        $this->assertCount(3, $result);
125
126
        $result = $this->repo->findList(2);
127
        $this->assertCount(2, $result);
128
129
        $result = $this->repo->findList(2, 1);
130
        $this->assertCount(2, $result);
131
132
        $this->assertCount(1, $this->repo->findList(2, 2));
133
134
        $result = $this->repo->findList(null, null, null, [], ShortUrlsOrdering::fromRawData([
135
            'orderBy' => ['visits' => 'DESC'],
136
        ]));
137
        $this->assertCount(3, $result);
138
        $this->assertSame($bar, $result[0]);
139
140
        $result = $this->repo->findList(null, null, null, [], null, new DateRange(null, Chronos::now()->subDays(2)));
141
        $this->assertCount(1, $result);
142
        $this->assertEquals(1, $this->repo->countList(null, [], new DateRange(null, Chronos::now()->subDays(2))));
143
        $this->assertSame($foo2, $result[0]);
144
145
        $this->assertCount(
146
            2,
147
            $this->repo->findList(null, null, null, [], null, new DateRange(Chronos::now()->subDays(2))),
148
        );
149
        $this->assertEquals(2, $this->repo->countList(null, [], new DateRange(Chronos::now()->subDays(2))));
150
    }
151
152
    /** @test */
153
    public function findListProperlyMapsFieldNamesToColumnNamesWhenOrdering(): void
154
    {
155
        $urls = ['a', 'z', 'c', 'b'];
156
        foreach ($urls as $url) {
157
            $this->getEntityManager()->persist(new ShortUrl($url));
158
        }
159
160
        $this->getEntityManager()->flush();
161
162
        $result = $this->repo->findList(null, null, null, [], ShortUrlsOrdering::fromRawData([
163
            'orderBy' => ['longUrl' => 'ASC'],
164
        ]));
165
166
        $this->assertCount(count($urls), $result);
167
        $this->assertEquals('a', $result[0]->getLongUrl());
168
        $this->assertEquals('b', $result[1]->getLongUrl());
169
        $this->assertEquals('c', $result[2]->getLongUrl());
170
        $this->assertEquals('z', $result[3]->getLongUrl());
171
    }
172
173
    /** @test */
174
    public function shortCodeIsInUseLooksForShortUrlInProperSetOfTables(): void
175
    {
176
        $shortUrlWithoutDomain = new ShortUrl('foo', ShortUrlMeta::fromRawData(['customSlug' => 'my-cool-slug']));
177
        $this->getEntityManager()->persist($shortUrlWithoutDomain);
178
179
        $shortUrlWithDomain = new ShortUrl(
180
            'foo',
181
            ShortUrlMeta::fromRawData(['domain' => 'doma.in', 'customSlug' => 'another-slug']),
182
        );
183
        $this->getEntityManager()->persist($shortUrlWithDomain);
184
185
        $this->getEntityManager()->flush();
186
187
        $this->assertTrue($this->repo->shortCodeIsInUse('my-cool-slug'));
188
        $this->assertFalse($this->repo->shortCodeIsInUse('my-cool-slug', 'doma.in'));
189
        $this->assertFalse($this->repo->shortCodeIsInUse('slug-not-in-use'));
190
        $this->assertFalse($this->repo->shortCodeIsInUse('another-slug'));
191
        $this->assertFalse($this->repo->shortCodeIsInUse('another-slug', 'example.com'));
192
        $this->assertTrue($this->repo->shortCodeIsInUse('another-slug', 'doma.in'));
193
    }
194
195
    /** @test */
196
    public function findOneLooksForShortUrlInProperSetOfTables(): void
197
    {
198
        $shortUrlWithoutDomain = new ShortUrl('foo', ShortUrlMeta::fromRawData(['customSlug' => 'my-cool-slug']));
199
        $this->getEntityManager()->persist($shortUrlWithoutDomain);
200
201
        $shortUrlWithDomain = new ShortUrl(
202
            'foo',
203
            ShortUrlMeta::fromRawData(['domain' => 'doma.in', 'customSlug' => 'another-slug']),
204
        );
205
        $this->getEntityManager()->persist($shortUrlWithDomain);
206
207
        $this->getEntityManager()->flush();
208
209
        $this->assertNotNull($this->repo->findOne('my-cool-slug'));
210
        $this->assertNull($this->repo->findOne('my-cool-slug', 'doma.in'));
211
        $this->assertNull($this->repo->findOne('slug-not-in-use'));
212
        $this->assertNull($this->repo->findOne('another-slug'));
213
        $this->assertNull($this->repo->findOne('another-slug', 'example.com'));
214
        $this->assertNotNull($this->repo->findOne('another-slug', 'doma.in'));
215
    }
216
217
    /** @test */
218
    public function findOneMatchingReturnsNullForNonExistingShortUrls(): void
219
    {
220
        $this->assertNull($this->repo->findOneMatching('', [], ShortUrlMeta::createEmpty()));
221
        $this->assertNull($this->repo->findOneMatching('foobar', [], ShortUrlMeta::createEmpty()));
222
        $this->assertNull($this->repo->findOneMatching('foobar', ['foo', 'bar'], ShortUrlMeta::createEmpty()));
223
        $this->assertNull($this->repo->findOneMatching('foobar', ['foo', 'bar'], ShortUrlMeta::fromRawData([
224
            'validSince' => Chronos::parse('2020-03-05 20:18:30'),
225
            'customSlug' => 'this_slug_does_not_exist',
226
        ])));
227
    }
228
229
    /** @test */
230
    public function findOneMatchingAppliesProperConditions(): void
231
    {
232
        $start = Chronos::parse('2020-03-05 20:18:30');
233
        $end = Chronos::parse('2021-03-05 20:18:30');
234
235
        $shortUrl = new ShortUrl('foo', ShortUrlMeta::fromRawData(['validSince' => $start]));
236
        $shortUrl->setTags($this->tagNamesToEntities($this->getEntityManager(), ['foo', 'bar']));
237
        $this->getEntityManager()->persist($shortUrl);
238
239
        $shortUrl2 = new ShortUrl('bar', ShortUrlMeta::fromRawData(['validUntil' => $end]));
240
        $this->getEntityManager()->persist($shortUrl2);
241
242
        $shortUrl3 = new ShortUrl('baz', ShortUrlMeta::fromRawData(['validSince' => $start, 'validUntil' => $end]));
243
        $this->getEntityManager()->persist($shortUrl3);
244
245
        $shortUrl4 = new ShortUrl('foo', ShortUrlMeta::fromRawData(['customSlug' => 'custom', 'validUntil' => $end]));
246
        $this->getEntityManager()->persist($shortUrl4);
247
248
        $shortUrl5 = new ShortUrl('foo', ShortUrlMeta::fromRawData(['maxVisits' => 3]));
249
        $this->getEntityManager()->persist($shortUrl5);
250
251
        $shortUrl6 = new ShortUrl('foo', ShortUrlMeta::fromRawData(['domain' => 'doma.in']));
252
        $this->getEntityManager()->persist($shortUrl6);
253
254
        $this->getEntityManager()->flush();
255
256
        $this->assertSame(
257
            $shortUrl,
258
            $this->repo->findOneMatching('foo', ['foo', 'bar'], ShortUrlMeta::fromRawData(['validSince' => $start])),
259
        );
260
        $this->assertSame(
261
            $shortUrl2,
262
            $this->repo->findOneMatching('bar', [], ShortUrlMeta::fromRawData(['validUntil' => $end])),
263
        );
264
        $this->assertSame(
265
            $shortUrl3,
266
            $this->repo->findOneMatching('baz', [], ShortUrlMeta::fromRawData([
267
                'validSince' => $start,
268
                'validUntil' => $end,
269
            ])),
270
        );
271
        $this->assertSame(
272
            $shortUrl4,
273
            $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData([
274
                'customSlug' => 'custom',
275
                'validUntil' => $end,
276
            ])),
277
        );
278
        $this->assertSame(
279
            $shortUrl5,
280
            $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData(['maxVisits' => 3])),
281
        );
282
        $this->assertSame(
283
            $shortUrl6,
284
            $this->repo->findOneMatching('foo', [], ShortUrlMeta::fromRawData(['domain' => 'doma.in'])),
285
        );
286
    }
287
288
    /** @test */
289
    public function findOneMatchingReturnsOldestOneWhenThereAreMultipleMatches(): void
290
    {
291
        $start = Chronos::parse('2020-03-05 20:18:30');
292
        $meta = ['validSince' => $start, 'maxVisits' => 50];
293
        $tags = ['foo', 'bar'];
294
        $tagEntities = $this->tagNamesToEntities($this->getEntityManager(), $tags);
295
296
        $shortUrl1 = new ShortUrl('foo', ShortUrlMeta::fromRawData($meta));
297
        $shortUrl1->setTags($tagEntities);
298
        $this->getEntityManager()->persist($shortUrl1);
299
300
        $shortUrl2 = new ShortUrl('foo', ShortUrlMeta::fromRawData($meta));
301
        $shortUrl2->setTags($tagEntities);
302
        $this->getEntityManager()->persist($shortUrl2);
303
304
        $shortUrl3 = new ShortUrl('foo', ShortUrlMeta::fromRawData($meta));
305
        $shortUrl3->setTags($tagEntities);
306
        $this->getEntityManager()->persist($shortUrl3);
307
308
        $this->getEntityManager()->flush();
309
310
        $this->assertSame(
311
            $shortUrl1,
312
            $this->repo->findOneMatching('foo', $tags, ShortUrlMeta::fromRawData($meta)),
313
        );
314
        $this->assertNotSame(
315
            $shortUrl2,
316
            $this->repo->findOneMatching('foo', $tags, ShortUrlMeta::fromRawData($meta)),
317
        );
318
        $this->assertNotSame(
319
            $shortUrl3,
320
            $this->repo->findOneMatching('foo', $tags, ShortUrlMeta::fromRawData($meta)),
321
        );
322
    }
323
}
324