validator = new AppwriteDomain(); } public function tearDown(): void { $this->validator = null; } public function testIsValid(): void { // Get the actual configured suffix from environment (in Docker it's sites.localhost) // But test both the environment value and the default value $envSuffix = \Utopia\System\System::getEnv('_APP_DOMAIN_SITES', defined('APP_DOMAIN_SITES_SUFFIX') ? APP_DOMAIN_SITES_SUFFIX : 'appwrite.network'); $functionsDomain = \Utopia\System\System::getEnv('_APP_DOMAIN_FUNCTIONS', 'functions.localhost'); // Valid domains for sites.localhost and functions.localhost (single-level subdomains only) $this->assertEquals(true, $this->validator->isValid('api.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('test.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('myapp.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('staging.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('prod.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('app123.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('test-app.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('my-awesome-app.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('a.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('x1.' . $envSuffix)); // Valid domains for functions domain (single-level subdomains only) $this->assertEquals(true, $this->validator->isValid('api.' . $functionsDomain)); $this->assertEquals(true, $this->validator->isValid('test.' . $functionsDomain)); $this->assertEquals(true, $this->validator->isValid('myapp.' . $functionsDomain)); // Case insensitive validation $this->assertEquals(true, $this->validator->isValid('API.' . strtoupper($envSuffix))); $this->assertEquals(true, $this->validator->isValid('Test.' . ucfirst($envSuffix))); $this->assertEquals(true, $this->validator->isValid('MyApp.' . $envSuffix)); // Invalid sub-subdomains for sites.localhost and functions.localhost (multiple levels not allowed) $this->assertEquals(false, $this->validator->isValid('api.dev.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('foo.bar.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('app.staging.test.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('sub.domain.example.' . $envSuffix)); // Invalid sub-subdomains for functions domain (multiple levels not allowed) $this->assertEquals(false, $this->validator->isValid('api.dev.' . $functionsDomain)); $this->assertEquals(false, $this->validator->isValid('foo.bar.' . $functionsDomain)); // Valid appwrite.network domains (one-level subdomains) - using environment variable $appwriteDomain = \Utopia\System\System::getEnv('_APP_DOMAIN_SITES', defined('APP_DOMAIN_SITES_SUFFIX') ? APP_DOMAIN_SITES_SUFFIX : 'appwrite.network'); $this->assertEquals(true, $this->validator->isValid('api.' . $appwriteDomain)); $this->assertEquals(true, $this->validator->isValid('test.' . $appwriteDomain)); $this->assertEquals(true, $this->validator->isValid('myapp.' . $appwriteDomain)); // Invalid sub-subdomains for appwrite.network $this->assertEquals(false, $this->validator->isValid('api.dev.' . $appwriteDomain)); $this->assertEquals(false, $this->validator->isValid('foo.bar.' . $appwriteDomain)); // Other domains should be valid (no multilevel restrictions) $this->assertEquals(true, $this->validator->isValid('example.com')); $this->assertEquals(true, $this->validator->isValid('api.example.com')); $this->assertEquals(true, $this->validator->isValid('test.google.com')); $this->assertEquals(true, $this->validator->isValid('app.github.io')); $this->assertEquals(true, $this->validator->isValid('myapp.herokuapp.com')); $this->assertEquals(true, $this->validator->isValid('sub.domain.example.com')); // Domains with spaces and invalid characters $this->assertEquals(false, $this->validator->isValid('my app.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('test .' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid(' api.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('api.' . $envSuffix . ' ')); $this->assertEquals(false, $this->validator->isValid('app@test.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('app#test.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('app$test.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('app%test.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('app_test.' . $envSuffix)); // Invalid formatting $this->assertEquals(false, $this->validator->isValid('.api.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('api.' . $envSuffix . '.')); $this->assertEquals(false, $this->validator->isValid('api..' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid($envSuffix)); $this->assertEquals(false, $this->validator->isValid('.' . $envSuffix . '.')); $this->assertEquals(false, $this->validator->isValid('..' . $envSuffix)); // Forbidden prefixes $this->assertEquals(false, $this->validator->isValid('commit-api.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('commit-test.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('commit-123.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('branch-api.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('branch-test.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('branch-123.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('COMMIT-api.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('BRANCH-test.' . $envSuffix)); // Valid domains that should not be confused with forbidden prefixes $this->assertEquals(true, $this->validator->isValid('commitment.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('branching.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('my-commit.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('my-branch.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('pre-commit.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('post-branch.' . $envSuffix)); // Edge cases with dots $this->assertEquals(false, $this->validator->isValid('.api.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('api..' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('api.' . $envSuffix . '.')); $this->assertEquals(false, $this->validator->isValid('.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('..' . $envSuffix)); // Just the suffix without subdomain $this->assertEquals(false, $this->validator->isValid($envSuffix)); $this->assertEquals(false, $this->validator->isValid('.' . $envSuffix)); // Empty and null values $this->assertEquals(false, $this->validator->isValid('')); $this->assertEquals(false, $this->validator->isValid(' ')); $this->assertEquals(false, $this->validator->isValid(null)); $this->assertEquals(false, $this->validator->isValid(false)); // Non-string types $this->assertEquals(false, $this->validator->isValid(123)); $this->assertEquals(false, $this->validator->isValid(12.34)); $this->assertEquals(false, $this->validator->isValid(['api.appwrite.network'])); $this->assertEquals(false, $this->validator->isValid((object)['domain' => 'api.appwrite.network'])); $this->assertEquals(false, $this->validator->isValid(true)); // Invalid subdomain formats $this->assertEquals(false, $this->validator->isValid('-api.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('api-.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('-test-.' . $envSuffix)); $this->assertEquals(false, $this->validator->isValid('-.' . $envSuffix)); // Too long subdomain (over 63 characters) $longSubdomain = str_repeat('a', 64) . '.' . $envSuffix; $this->assertEquals(false, $this->validator->isValid($longSubdomain)); // Exactly 63 characters should be valid $maxLengthSubdomain = str_repeat('a', 63) . '.' . $envSuffix; $this->assertEquals(true, $this->validator->isValid($maxLengthSubdomain)); // Single character subdomain should be valid $this->assertEquals(true, $this->validator->isValid('a.' . $envSuffix)); // Numbers in subdomain $this->assertEquals(true, $this->validator->isValid('123.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('api123.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('123api.' . $envSuffix)); // Mixed case with hyphens $this->assertEquals(true, $this->validator->isValid('My-Test-App.' . $envSuffix)); $this->assertEquals(true, $this->validator->isValid('app-v2.' . $envSuffix)); } public function testGetType(): void { $this->assertEquals('string', $this->validator->getType()); } public function testIsArray(): void { $this->assertEquals(false, $this->validator->isArray()); } public function testGetDescription(): void { $description = $this->validator->getDescription(); $this->assertIsString($description); // Get the environment suffix to check it's in the description $envSuffix = \Utopia\System\System::getEnv('_APP_DOMAIN_SITES', defined('APP_DOMAIN_SITES_SUFFIX') ? APP_DOMAIN_SITES_SUFFIX : 'appwrite.network'); $this->assertStringContainsString($envSuffix, $description); $this->assertStringContainsString('one-level subdomain', $description); } }