Completed
Push — develop ( 34d8b3...fd6151 )
by Alejandro
22s queued 14s
created

invalidAddressLogsWarning()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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