ipLocationResolver = $this->createMock(IpLocationResolverInterface::class); $this->em = $this->createMock(EntityManagerInterface::class); $this->logger = $this->createMock(LoggerInterface::class); $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); $this->dbUpdater = $this->createMock(DbUpdaterInterface::class); $this->locateVisit = new LocateVisit( $this->ipLocationResolver, $this->em, $this->logger, $this->dbUpdater, $this->eventDispatcher, ); } /** @test */ public function invalidVisitLogsWarning(): void { $event = new UrlVisited('123'); $this->em->expects($this->once())->method('find')->with( $this->equalTo(Visit::class), $this->equalTo('123'), )->willReturn(null); $this->em->expects($this->never())->method('flush'); $this->logger->expects($this->once())->method('warning')->with( $this->equalTo('Tried to locate visit with id "{visitId}", but it does not exist.'), $this->equalTo(['visitId' => 123]), ); $this->eventDispatcher->expects($this->never())->method('dispatch')->with( $this->equalTo(new VisitLocated('123')), ); $this->ipLocationResolver->expects($this->never())->method('resolveIpLocation'); ($this->locateVisit)($event); } /** @test */ public function nonExistingGeoLiteDbLogsWarning(): void { $event = new UrlVisited('123'); $this->em->expects($this->once())->method('find')->with( $this->equalTo(Visit::class), $this->equalTo('123'), )->willReturn(Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', ''))); $this->em->expects($this->never())->method('flush'); $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(false); $this->logger->expects($this->once())->method('warning')->with( $this->equalTo('Tried to locate visit with id "{visitId}", but a GeoLite2 db was not found.'), $this->equalTo(['visitId' => 123]), ); $this->eventDispatcher->expects($this->once())->method('dispatch')->with( $this->equalTo(new VisitLocated('123')), ); $this->ipLocationResolver->expects($this->never())->method('resolveIpLocation'); ($this->locateVisit)($event); } /** @test */ public function invalidAddressLogsWarning(): void { $event = new UrlVisited('123'); $this->em->expects($this->once())->method('find')->with( $this->equalTo(Visit::class), $this->equalTo('123'), )->willReturn(Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', ''))); $this->em->expects($this->never())->method('flush'); $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); $this->ipLocationResolver->expects( $this->once(), )->method('resolveIpLocation')->withAnyParameters()->willThrowException(WrongIpException::fromIpAddress('')); $this->logger->expects($this->once())->method('warning')->with( $this->equalTo('Tried to locate visit with id "{visitId}", but its address seems to be wrong. {e}'), $this->isType('array'), ); $this->eventDispatcher->expects($this->once())->method('dispatch')->with( $this->equalTo(new VisitLocated('123')), ); ($this->locateVisit)($event); } /** @test */ public function unhandledExceptionLogsError(): void { $event = new UrlVisited('123'); $this->em->expects($this->once())->method('find')->with( $this->equalTo(Visit::class), $this->equalTo('123'), )->willReturn(Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', ''))); $this->em->expects($this->never())->method('flush'); $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); $this->ipLocationResolver->expects( $this->once(), )->method('resolveIpLocation')->withAnyParameters()->willThrowException(new OutOfRangeException()); $this->logger->expects($this->once())->method('error')->with( $this->equalTo('An unexpected error occurred while trying to locate visit with id "{visitId}". {e}'), $this->isType('array'), ); $this->eventDispatcher->expects($this->once())->method('dispatch')->with( $this->equalTo(new VisitLocated('123')), ); ($this->locateVisit)($event); } /** * @test * @dataProvider provideNonLocatableVisits */ public function nonLocatableVisitsResolveToEmptyLocations(Visit $visit): void { $event = new UrlVisited('123'); $this->em->expects($this->once())->method('find')->with( $this->equalTo(Visit::class), $this->equalTo('123'), )->willReturn($visit); $this->em->expects($this->once())->method('flush'); $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); $this->ipLocationResolver->expects($this->never())->method('resolveIpLocation'); $this->eventDispatcher->expects($this->once())->method('dispatch')->with( $this->equalTo(new VisitLocated('123')), ); $this->logger->expects($this->never())->method('warning'); ($this->locateVisit)($event); self::assertEquals($visit->getVisitLocation(), VisitLocation::fromGeolocation(Location::emptyInstance())); } public function provideNonLocatableVisits(): iterable { $shortUrl = ShortUrl::createEmpty(); yield 'null IP' => [Visit::forValidShortUrl($shortUrl, new Visitor('', '', null, ''))]; yield 'empty IP' => [Visit::forValidShortUrl($shortUrl, new Visitor('', '', '', ''))]; yield 'localhost' => [Visit::forValidShortUrl($shortUrl, new Visitor('', '', IpAddress::LOCALHOST, ''))]; } /** * @test * @dataProvider provideIpAddresses */ public function locatableVisitsResolveToLocation(Visit $visit, ?string $originalIpAddress): void { $ipAddr = $originalIpAddress ?? $visit->getRemoteAddr(); $location = new Location('', '', '', '', 0.0, 0.0, ''); $event = UrlVisited::withOriginalIpAddress('123', $originalIpAddress); $this->em->expects($this->once())->method('find')->with( $this->equalTo(Visit::class), $this->equalTo('123'), )->willReturn($visit); $this->em->expects($this->once())->method('flush'); $this->dbUpdater->expects($this->once())->method('databaseFileExists')->withAnyParameters()->willReturn(true); $this->ipLocationResolver->expects($this->once())->method('resolveIpLocation')->with( $this->equalTo($ipAddr), )->willReturn($location); $this->eventDispatcher->expects($this->once())->method('dispatch')->with( $this->equalTo(new VisitLocated('123')), ); $this->logger->expects($this->never())->method('warning'); ($this->locateVisit)($event); self::assertEquals($visit->getVisitLocation(), VisitLocation::fromGeolocation($location)); } public function provideIpAddresses(): iterable { yield 'no original IP address' => [ Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', '')), null, ]; yield 'original IP address' => [ Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', '')), '1.2.3.4', ]; yield 'base url' => [Visit::forBasePath(new Visitor('', '', '1.2.3.4', '')), '1.2.3.4']; yield 'invalid short url' => [Visit::forInvalidShortUrl(new Visitor('', '', '1.2.3.4', '')), '1.2.3.4']; yield 'regular not found' => [Visit::forRegularNotFound(new Visitor('', '', '1.2.3.4', '')), '1.2.3.4']; } }