registry = $this->createMock(TenantRegistry::class); } #[Test] public function itExtractsSubdomainFromHost(): void { $resolver = new TenantResolver($this->registry, 'classeo.local'); $subdomain = $resolver->extractSubdomain('ecole-alpha.classeo.local'); self::assertSame('ecole-alpha', $subdomain); } #[Test] public function itExtractsSubdomainFromHostWithPort(): void { $resolver = new TenantResolver($this->registry, 'classeo.local'); $subdomain = $resolver->extractSubdomain('ecole-alpha.classeo.local:8080'); self::assertSame('ecole-alpha', $subdomain); } #[Test] public function itReturnsNullForMainDomainWithoutSubdomain(): void { $resolver = new TenantResolver($this->registry, 'classeo.local'); $subdomain = $resolver->extractSubdomain('classeo.local'); self::assertNull($subdomain); } #[Test] public function itReturnsNullForWwwSubdomain(): void { $resolver = new TenantResolver($this->registry, 'classeo.local'); $subdomain = $resolver->extractSubdomain('www.classeo.local'); self::assertNull($subdomain); } #[Test] public function itReturnsNullForApiSubdomain(): void { $resolver = new TenantResolver($this->registry, 'classeo.local'); $subdomain = $resolver->extractSubdomain('api.classeo.local'); self::assertNull($subdomain); } #[Test] public function itResolvesValidTenantFromHost(): void { $tenantId = TenantId::fromString('a1b2c3d4-e5f6-7890-abcd-ef1234567890'); $config = new TenantConfig( tenantId: $tenantId, subdomain: 'ecole-alpha', databaseUrl: 'postgresql://user:pass@localhost:5432/classeo_alpha', ); $this->registry->method('getBySubdomain') ->with('ecole-alpha') ->willReturn($config); $resolver = new TenantResolver($this->registry, 'classeo.local'); $resolved = $resolver->resolve('ecole-alpha.classeo.local'); self::assertTrue($tenantId->equals($resolved->tenantId)); } #[Test] public function itThrowsExceptionForNonExistentTenant(): void { $this->registry->method('getBySubdomain') ->with('ecole-inexistant') ->willThrowException(TenantNotFoundException::withSubdomain('ecole-inexistant')); $resolver = new TenantResolver($this->registry, 'classeo.local'); $this->expectException(TenantNotFoundException::class); $resolver->resolve('ecole-inexistant.classeo.local'); } #[Test] public function itThrowsExceptionWhenNoSubdomainInHost(): void { $resolver = new TenantResolver($this->registry, 'classeo.local'); $this->expectException(TenantNotFoundException::class); $resolver->resolve('classeo.local'); } #[Test] #[DataProvider('reservedSubdomainsProvider')] public function itRejectsReservedSubdomains(string $subdomain): void { $resolver = new TenantResolver($this->registry, 'classeo.local'); $this->expectException(TenantNotFoundException::class); $resolver->resolve("{$subdomain}.classeo.local"); } /** * @return iterable */ public static function reservedSubdomainsProvider(): iterable { yield 'www' => ['www']; yield 'api' => ['api']; yield 'admin' => ['admin']; yield 'static' => ['static']; yield 'cdn' => ['cdn']; yield 'mail' => ['mail']; } }