em = $this->createStub(EntityManagerInterface::class); $this->repo = $this->createMock(DomainRepositoryInterface::class); $this->domainService = new DomainService( $this->em, new UrlShortenerOptions(defaultDomain: 'default.com'), $this->repo, ); } #[Test, DataProvider('provideExcludedDomains')] public function listDomainsDelegatesIntoRepository(array $domains, array $expectedResult, ApiKey|null $apiKey): void { $this->repo->expects($this->once())->method('findDomains')->with($apiKey)->willReturn($domains); $result = $this->domainService->listDomains($apiKey); self::assertEquals($expectedResult, $result); } public static function provideExcludedDomains(): iterable { $default = DomainItem::forDefaultDomain('default.com', new EmptyNotFoundRedirectConfig()); $adminApiKey = ApiKey::create(); $domain = Domain::withAuthority(''); $domain->setId('123'); $domainSpecificApiKey = ApiKey::fromMeta( ApiKeyMeta::withRoles(RoleDefinition::forDomain($domain)), ); yield 'empty list without API key' => [[], [$default], null]; yield 'one item without API key' => [ [Domain::withAuthority('bar.com')], [$default, DomainItem::forNonDefaultDomain(Domain::withAuthority('bar.com'))], null, ]; yield 'multiple items without API key' => [ [Domain::withAuthority('foo.com'), Domain::withAuthority('bar.com')], [ $default, DomainItem::forNonDefaultDomain(Domain::withAuthority('foo.com')), DomainItem::forNonDefaultDomain(Domain::withAuthority('bar.com')), ], null, ]; yield 'empty list with admin API key' => [[], [$default], $adminApiKey]; yield 'one item with admin API key' => [ [Domain::withAuthority('bar.com')], [$default, DomainItem::forNonDefaultDomain(Domain::withAuthority('bar.com'))], $adminApiKey, ]; yield 'multiple items with admin API key' => [ [Domain::withAuthority('foo.com'), Domain::withAuthority('bar.com')], [ $default, DomainItem::forNonDefaultDomain(Domain::withAuthority('foo.com')), DomainItem::forNonDefaultDomain(Domain::withAuthority('bar.com')), ], $adminApiKey, ]; yield 'empty list with domain-specific API key' => [[], [], $domainSpecificApiKey]; yield 'one item with domain-specific API key' => [ [Domain::withAuthority('bar.com')], [DomainItem::forNonDefaultDomain(Domain::withAuthority('bar.com'))], $domainSpecificApiKey, ]; yield 'multiple items with domain-specific API key' => [ [Domain::withAuthority('foo.com'), Domain::withAuthority('bar.com')], [ DomainItem::forNonDefaultDomain(Domain::withAuthority('foo.com')), DomainItem::forNonDefaultDomain(Domain::withAuthority('bar.com')), ], $domainSpecificApiKey, ]; } #[Test, AllowMockObjectsWithoutExpectations] public function getDomainThrowsExceptionWhenDomainIsNotFound(): void { $this->em->method('find')->willReturnMap([[Domain::class, '123', null]]); $this->expectException(DomainNotFoundException::class); $this->domainService->getDomain('123'); } #[Test, AllowMockObjectsWithoutExpectations] public function getDomainReturnsEntityWhenFound(): void { $domain = Domain::withAuthority(''); $this->em->method('find')->willReturnMap([[Domain::class, '123', $domain]]); $result = $this->domainService->getDomain('123'); self::assertSame($domain, $result); } #[Test, DataProvider('provideFoundDomains')] public function getOrCreateAlwaysPersistsDomain(Domain|null $foundDomain, ApiKey|null $apiKey): void { $authority = 'example.com'; $this->repo->expects($this->once())->method('findOneByAuthority')->with($authority, $apiKey)->willReturn( $foundDomain, ); $result = $this->domainService->getOrCreate($authority, $apiKey); if ($foundDomain !== null) { self::assertSame($result, $foundDomain); } } #[Test] public function getOrCreateThrowsExceptionForApiKeysWithDomainRole(): void { $authority = 'example.com'; $domain = Domain::withAuthority($authority); $domain->setId('1'); $apiKey = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forDomain($domain))); $this->repo->expects($this->once())->method('findOneByAuthority')->with($authority, $apiKey)->willReturn(null); $this->expectException(DomainNotFoundException::class); $this->domainService->getOrCreate($authority, $apiKey); } #[Test, DataProvider('provideFoundDomains')] public function configureNotFoundRedirectsConfiguresFetchedDomain( Domain|null $foundDomain, ApiKey|null $apiKey, ): void { $authority = 'example.com'; $this->repo->expects($this->once())->method('findOneByAuthority')->with($authority, $apiKey)->willReturn( $foundDomain, ); $result = $this->domainService->configureNotFoundRedirects($authority, NotFoundRedirects::withRedirects( 'foo.com', 'bar.com', 'baz.com', ), $apiKey); if ($foundDomain !== null) { self::assertSame($result, $foundDomain); } self::assertEquals('foo.com', $result->baseUrlRedirect); self::assertEquals('bar.com', $result->regular404Redirect); self::assertEquals('baz.com', $result->invalidShortUrlRedirect); } public static function provideFoundDomains(): iterable { $domain = Domain::withAuthority(''); $adminApiKey = ApiKey::create(); $authorApiKey = ApiKey::fromMeta(ApiKeyMeta::withRoles(RoleDefinition::forAuthoredShortUrls())); yield 'domain not found and no API key' => [null, null]; yield 'domain found and no API key' => [$domain, null]; yield 'domain not found and admin API key' => [null, $adminApiKey]; yield 'domain found and admin API key' => [$domain, $adminApiKey]; yield 'domain not found and author API key' => [null, $authorApiKey]; yield 'domain found and author API key' => [$domain, $authorApiKey]; } }