Passed
Pull Request — master (#417)
by Alejandro
06:44
created

LocateShortUrlVisitTest::setUp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 9
c 2
b 0
f 0
dl 0
loc 12
rs 9.9666
cc 1
nc 1
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace ShlinkioTest\Shlink\Core\EventDispatcher;
5
6
use Doctrine\ORM\EntityManagerInterface;
7
use PHPUnit\Framework\TestCase;
8
use Prophecy\Argument;
9
use Prophecy\Prophecy\ObjectProphecy;
10
use Psr\Log\LoggerInterface;
11
use Shlinkio\Shlink\CLI\Exception\GeolocationDbUpdateFailedException;
12
use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdaterInterface;
13
use Shlinkio\Shlink\Common\Exception\WrongIpException;
14
use Shlinkio\Shlink\Common\IpGeolocation\IpLocationResolverInterface;
15
use Shlinkio\Shlink\Common\IpGeolocation\Model\Location;
16
use Shlinkio\Shlink\Common\Util\IpAddress;
17
use Shlinkio\Shlink\Core\Entity\ShortUrl;
18
use Shlinkio\Shlink\Core\Entity\Visit;
19
use Shlinkio\Shlink\Core\Entity\VisitLocation;
20
use Shlinkio\Shlink\Core\EventDispatcher\LocateShortUrlVisit;
21
use Shlinkio\Shlink\Core\EventDispatcher\ShortUrlVisited;
22
use Shlinkio\Shlink\Core\Model\Visitor;
23
use Shlinkio\Shlink\Core\Visit\Model\UnknownVisitLocation;
24
25
class LocateShortUrlVisitTest extends TestCase
26
{
27
    /** @var LocateShortUrlVisit */
28
    private $locateVisit;
29
    /** @var ObjectProphecy */
30
    private $ipLocationResolver;
31
    /** @var ObjectProphecy */
32
    private $em;
33
    /** @var ObjectProphecy */
34
    private $logger;
35
    /** @var ObjectProphecy */
36
    private $dbUpdater;
37
38
    public function setUp(): void
39
    {
40
        $this->ipLocationResolver = $this->prophesize(IpLocationResolverInterface::class);
41
        $this->em = $this->prophesize(EntityManagerInterface::class);
42
        $this->logger = $this->prophesize(LoggerInterface::class);
43
        $this->dbUpdater = $this->prophesize(GeolocationDbUpdaterInterface::class);
44
45
        $this->locateVisit = new LocateShortUrlVisit(
46
            $this->ipLocationResolver->reveal(),
47
            $this->em->reveal(),
48
            $this->logger->reveal(),
49
            $this->dbUpdater->reveal()
50
        );
51
    }
52
53
    /** @test */
54
    public function invalidVisitLogsWarning(): void
55
    {
56
        $event = new ShortUrlVisited('123');
57
        $findVisit = $this->em->find(Visit::class, '123')->willReturn(null);
58
        $logWarning = $this->logger->warning('Tried to locate visit with id "123", but it does not exist.');
59
60
        ($this->locateVisit)($event);
61
62
        $findVisit->shouldHaveBeenCalledOnce();
63
        $this->em->flush(Argument::cetera())->shouldNotHaveBeenCalled();
64
        $this->ipLocationResolver->resolveIpLocation(Argument::cetera())->shouldNotHaveBeenCalled();
65
        $logWarning->shouldHaveBeenCalled();
66
    }
67
68
    /** @test */
69
    public function invalidAddressLogsWarning(): void
70
    {
71
        $event = new ShortUrlVisited('123');
72
        $findVisit = $this->em->find(Visit::class, '123')->willReturn(
73
            new Visit(new ShortUrl(''), new Visitor('', '', '1.2.3.4'))
74
        );
75
        $resolveLocation = $this->ipLocationResolver->resolveIpLocation(Argument::cetera())->willThrow(
76
            WrongIpException::class
77
        );
78
        $logWarning = $this->logger->warning(
79
            Argument::containingString('Tried to locate visit with id "123", but its address seems to be wrong.'),
80
            Argument::type('array')
81
        );
82
83
        ($this->locateVisit)($event);
84
85
        $findVisit->shouldHaveBeenCalledOnce();
86
        $resolveLocation->shouldHaveBeenCalledOnce();
87
        $logWarning->shouldHaveBeenCalled();
88
        $this->em->flush(Argument::cetera())->shouldNotHaveBeenCalled();
89
    }
90
91
    /**
92
     * @test
93
     * @dataProvider provideNonLocatableVisits
94
     */
95
    public function nonLocatableVisitsResolveToEmptyLocations(Visit $visit): void
96
    {
97
        $event = new ShortUrlVisited('123');
98
        $findVisit = $this->em->find(Visit::class, '123')->willReturn($visit);
99
        $flush = $this->em->flush($visit)->will(function () {
100
        });
101
        $resolveIp = $this->ipLocationResolver->resolveIpLocation(Argument::any());
102
103
        ($this->locateVisit)($event);
104
105
        $this->assertEquals($visit->getVisitLocation(), new VisitLocation(Location::emptyInstance()));
106
        $findVisit->shouldHaveBeenCalledOnce();
107
        $flush->shouldHaveBeenCalledOnce();
108
        $resolveIp->shouldNotHaveBeenCalled();
109
        $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled();
110
    }
111
112
    public function provideNonLocatableVisits(): iterable
113
    {
114
        $shortUrl = new ShortUrl('');
115
116
        yield 'null IP' => [new Visit($shortUrl, new Visitor('', '', null))];
117
        yield 'empty IP' => [new Visit($shortUrl, new Visitor('', '', ''))];
118
        yield 'localhost' => [new Visit($shortUrl, new Visitor('', '', IpAddress::LOCALHOST))];
119
    }
120
121
    /** @test */
122
    public function locatableVisitsResolveToLocation(): void
123
    {
124
        $ipAddr = '1.2.3.0';
125
        $visit = new Visit(new ShortUrl(''), new Visitor('', '', $ipAddr));
126
        $location = new Location('', '', '', '', 0.0, 0.0, '');
127
        $event = new ShortUrlVisited('123');
128
129
        $findVisit = $this->em->find(Visit::class, '123')->willReturn($visit);
130
        $flush = $this->em->flush($visit)->will(function () {
131
        });
132
        $resolveIp = $this->ipLocationResolver->resolveIpLocation($ipAddr)->willReturn($location);
133
134
        ($this->locateVisit)($event);
135
136
        $this->assertEquals($visit->getVisitLocation(), new VisitLocation($location));
137
        $findVisit->shouldHaveBeenCalledOnce();
138
        $flush->shouldHaveBeenCalledOnce();
139
        $resolveIp->shouldHaveBeenCalledOnce();
140
        $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled();
141
    }
142
143
    /** @test */
144
    public function errorWhenUpdatingGeoliteWithExistingCopyLogsWarning(): void
145
    {
146
        $e = GeolocationDbUpdateFailedException::create(true);
147
        $ipAddr = '1.2.3.0';
148
        $visit = new Visit(new ShortUrl(''), new Visitor('', '', $ipAddr));
149
        $location = new Location('', '', '', '', 0.0, 0.0, '');
150
        $event = new ShortUrlVisited('123');
151
152
        $findVisit = $this->em->find(Visit::class, '123')->willReturn($visit);
153
        $flush = $this->em->flush($visit)->will(function () {
154
        });
155
        $resolveIp = $this->ipLocationResolver->resolveIpLocation($ipAddr)->willReturn($location);
156
        $checkUpdateDb = $this->dbUpdater->checkDbUpdate(Argument::cetera())->willThrow($e);
157
158
        ($this->locateVisit)($event);
159
160
        $this->assertEquals($visit->getVisitLocation(), new VisitLocation($location));
161
        $findVisit->shouldHaveBeenCalledOnce();
162
        $flush->shouldHaveBeenCalledOnce();
163
        $resolveIp->shouldHaveBeenCalledOnce();
164
        $checkUpdateDb->shouldHaveBeenCalledOnce();
165
        $this->logger->warning(
166
            'GeoLite2 database update failed. Proceeding with old version. {e}',
167
            ['e' => $e]
168
        )->shouldHaveBeenCalledOnce();
169
    }
170
171
    /** @test */
172
    public function errorWhenDownloadingGeoliteCancelsLocation(): void
173
    {
174
        $e = GeolocationDbUpdateFailedException::create(false);
175
        $ipAddr = '1.2.3.0';
176
        $visit = new Visit(new ShortUrl(''), new Visitor('', '', $ipAddr));
177
        $location = new Location('', '', '', '', 0.0, 0.0, '');
178
        $event = new ShortUrlVisited('123');
179
180
        $findVisit = $this->em->find(Visit::class, '123')->willReturn($visit);
181
        $flush = $this->em->flush($visit)->will(function () {
182
        });
183
        $resolveIp = $this->ipLocationResolver->resolveIpLocation($ipAddr)->willReturn($location);
184
        $checkUpdateDb = $this->dbUpdater->checkDbUpdate(Argument::cetera())->willThrow($e);
185
        $logError = $this->logger->error(
186
            'GeoLite2 database download failed. It is not possible to locate visit with id 123. {e}',
187
            ['e' => $e]
188
        );
189
190
        ($this->locateVisit)($event);
191
192
        $this->assertEquals($visit->getVisitLocation(), new UnknownVisitLocation());
193
        $findVisit->shouldHaveBeenCalledOnce();
194
        $flush->shouldNotHaveBeenCalled();
195
        $resolveIp->shouldNotHaveBeenCalled();
196
        $checkUpdateDb->shouldHaveBeenCalledOnce();
197
        $logError->shouldHaveBeenCalledOnce();
198
    }
199
}
200