em = $this->createMock(EntityManagerInterface::class); $this->helper = new VisitsStatsHelper($this->em); } #[Test, DataProvider('provideCounts')] public function returnsExpectedVisitsStats(int $expectedCount, ?ApiKey $apiKey): void { $callCount = 0; $visitsCountRepo = $this->createMock(ShortUrlVisitsCountRepository::class); $visitsCountRepo->expects($this->exactly(2))->method('countNonOrphanVisits')->willReturnCallback( function (VisitsCountFiltering $options) use ($expectedCount, $apiKey, &$callCount) { Assert::assertEquals($callCount !== 0, $options->excludeBots); Assert::assertEquals($apiKey, $options->apiKey); $callCount++; return $expectedCount * 3; }, ); $orphanVisitsCountRepo = $this->createMock(OrphanVisitsCountRepository::class); $orphanVisitsCountRepo->expects($this->exactly(2))->method('countOrphanVisits')->with( $this->isInstanceOf(VisitsCountFiltering::class), )->willReturn($expectedCount); $this->em->expects($this->exactly(2))->method('getRepository')->willReturnMap([ [OrphanVisitsCount::class, $orphanVisitsCountRepo], [ShortUrlVisitsCount::class, $visitsCountRepo], ]); $stats = $this->helper->getVisitsStats($apiKey); self::assertEquals(new VisitsStats($expectedCount * 3, $expectedCount), $stats); } public static function provideCounts(): iterable { return [ ...array_map(fn (int $value) => [$value, null], range(0, 50, 5)), ...array_map(fn (int $value) => [$value, ApiKey::create()], range(0, 18, 3)), ]; } #[Test, DataProviderExternal(ApiKeyDataProviders::class, 'adminApiKeysProvider')] public function infoReturnsVisitsForCertainShortCode(?ApiKey $apiKey): void { $shortCode = '123ABC'; $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); $spec = $apiKey?->spec(); $repo = $this->createMock(ShortUrlRepository::class); $repo->expects($this->once())->method('shortCodeIsInUse')->with($identifier, $spec)->willReturn(true); $list = array_map( static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()), range(0, 1), ); $repo2 = $this->createMock(VisitRepository::class); $repo2->method('findVisitsByShortCode')->with( $identifier, $this->isInstanceOf(VisitsListFiltering::class), )->willReturn($list); $repo2->method('countVisitsByShortCode')->with( $identifier, $this->isInstanceOf(VisitsCountFiltering::class), )->willReturn(1); $this->em->expects($this->exactly(2))->method('getRepository')->willReturnMap([ [ShortUrl::class, $repo], [Visit::class, $repo2], ]); $paginator = $this->helper->visitsForShortUrl($identifier, new VisitsParams(), $apiKey); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); } #[Test] public function throwsExceptionWhenRequestingVisitsForInvalidShortCode(): void { $shortCode = '123ABC'; $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode); $repo = $this->createMock(ShortUrlRepository::class); $repo->expects($this->once())->method('shortCodeIsInUse')->with($identifier, null)->willReturn(false); $this->em->expects($this->once())->method('getRepository')->with(ShortUrl::class)->willReturn($repo); $this->expectException(ShortUrlNotFoundException::class); $this->helper->visitsForShortUrl($identifier, new VisitsParams()); } #[Test] public function throwsExceptionWhenRequestingVisitsForInvalidTag(): void { $tag = 'foo'; $apiKey = ApiKey::create(); $repo = $this->createMock(TagRepository::class); $repo->expects($this->once())->method('tagExists')->with($tag, $apiKey)->willReturn(false); $this->em->expects($this->once())->method('getRepository')->with(Tag::class)->willReturn($repo); $this->expectException(TagNotFoundException::class); $this->helper->visitsForTag($tag, new VisitsParams(), $apiKey); } #[Test, DataProviderExternal(ApiKeyDataProviders::class, 'adminApiKeysProvider')] public function visitsForTagAreReturnedAsExpected(?ApiKey $apiKey): void { $tag = 'foo'; $repo = $this->createMock(TagRepository::class); $repo->expects($this->once())->method('tagExists')->with($tag, $apiKey)->willReturn(true); $list = array_map( static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()), range(0, 1), ); $repo2 = $this->createMock(VisitRepository::class); $repo2->method('findVisitsByTag')->with($tag, $this->isInstanceOf(VisitsListFiltering::class))->willReturn( $list, ); $repo2->method('countVisitsByTag')->with($tag, $this->isInstanceOf(VisitsCountFiltering::class))->willReturn(1); $this->em->expects($this->exactly(2))->method('getRepository')->willReturnMap([ [Tag::class, $repo], [Visit::class, $repo2], ]); $paginator = $this->helper->visitsForTag($tag, new VisitsParams(), $apiKey); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); } #[Test] public function throwsExceptionWhenRequestingVisitsForInvalidDomain(): void { $domain = 'foo.com'; $apiKey = ApiKey::create(); $repo = $this->createMock(DomainRepository::class); $repo->expects($this->once())->method('domainExists')->with($domain, $apiKey)->willReturn(false); $this->em->expects($this->once())->method('getRepository')->with(Domain::class)->willReturn($repo); $this->expectException(DomainNotFoundException::class); $this->helper->visitsForDomain($domain, new VisitsParams(), $apiKey); } #[Test, DataProviderExternal(ApiKeyDataProviders::class, 'adminApiKeysProvider')] public function visitsForNonDefaultDomainAreReturnedAsExpected(?ApiKey $apiKey): void { $domain = 'foo.com'; $repo = $this->createMock(DomainRepository::class); $repo->expects($this->once())->method('domainExists')->with($domain, $apiKey)->willReturn(true); $list = array_map( static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()), range(0, 1), ); $repo2 = $this->createMock(VisitRepository::class); $repo2->method('findVisitsByDomain')->with( $domain, $this->isInstanceOf(VisitsListFiltering::class), )->willReturn($list); $repo2->method('countVisitsByDomain')->with( $domain, $this->isInstanceOf(VisitsCountFiltering::class), )->willReturn(1); $this->em->expects($this->exactly(2))->method('getRepository')->willReturnMap([ [Domain::class, $repo], [Visit::class, $repo2], ]); $paginator = $this->helper->visitsForDomain($domain, new VisitsParams(), $apiKey); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); } #[Test, DataProviderExternal(ApiKeyDataProviders::class, 'adminApiKeysProvider')] public function visitsForDefaultDomainAreReturnedAsExpected(?ApiKey $apiKey): void { $repo = $this->createMock(DomainRepository::class); $repo->expects($this->never())->method('domainExists'); $list = array_map( static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()), range(0, 1), ); $repo2 = $this->createMock(VisitRepository::class); $repo2->method('findVisitsByDomain')->with( 'DEFAULT', $this->isInstanceOf(VisitsListFiltering::class), )->willReturn($list); $repo2->method('countVisitsByDomain')->with( 'DEFAULT', $this->isInstanceOf(VisitsCountFiltering::class), )->willReturn(1); $this->em->expects($this->exactly(2))->method('getRepository')->willReturnMap([ [Domain::class, $repo], [Visit::class, $repo2], ]); $paginator = $this->helper->visitsForDomain('DEFAULT', new VisitsParams(), $apiKey); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); } #[Test] public function orphanVisitsAreReturnedAsExpected(): void { $list = array_map(static fn () => Visit::forBasePath(Visitor::emptyInstance()), range(0, 3)); $repo = $this->createMock(VisitRepository::class); $repo->expects($this->once())->method('countOrphanVisits')->with( $this->isInstanceOf(OrphanVisitsCountFiltering::class), )->willReturn(count($list)); $repo->expects($this->once())->method('findOrphanVisits')->with( $this->isInstanceOf(OrphanVisitsListFiltering::class), )->willReturn($list); $this->em->expects($this->once())->method('getRepository')->with(Visit::class)->willReturn($repo); $paginator = $this->helper->orphanVisits(new OrphanVisitsParams()); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); } #[Test] public function nonOrphanVisitsAreReturnedAsExpected(): void { $list = array_map( static fn () => Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::emptyInstance()), range(0, 3), ); $repo = $this->createMock(VisitRepository::class); $repo->expects($this->once())->method('countNonOrphanVisits')->with( $this->isInstanceOf(VisitsCountFiltering::class), )->willReturn(count($list)); $repo->expects($this->once())->method('findNonOrphanVisits')->with( $this->isInstanceOf(VisitsListFiltering::class), )->willReturn($list); $this->em->expects($this->once())->method('getRepository')->with(Visit::class)->willReturn($repo); $paginator = $this->helper->nonOrphanVisits(new VisitsParams()); self::assertEquals($list, ArrayUtils::iteratorToArray($paginator->getCurrentPageResults())); } }