@@ -43,47 +43,114 @@ private function resetTestState(): void {
4343
4444 /**
4545 * Clean HTTPS domains are accepted and normalized.
46+ *
47+ * @param string $domain Domain to validate.
48+ * @param string $expected Expected normalized domain.
4649 */
47- public function test_accepts_clean_https_domains (): void {
48- $ result = es_optimizer_validate_single_domain ( 'https://Fonts.GStatic.com/ ' );
50+ #[DataProvider( 'acceptedDomainProvider ' )]
51+ public function test_accepts_clean_https_domains ( string $ domain , string $ expected ): void {
52+ $ result = es_optimizer_validate_single_domain ( $ domain );
4953
5054 $ this ->assertTrue ( $ result ['valid ' ] );
51- $ this ->assertSame ( 'https://fonts.gstatic.com ' , $ result ['domain ' ] );
55+ $ this ->assertSame ( $ expected , $ result ['domain ' ] );
56+ }
57+
58+ /**
59+ * Provide accepted domains.
60+ *
61+ * @return array<string, array{domain: string, expected: string}>
62+ */
63+ public static function acceptedDomainProvider (): array {
64+ return array (
65+ 'uppercase-with-root-path ' => array (
66+ 'domain ' => 'https://Fonts.GStatic.com/ ' ,
67+ 'expected ' => 'https://fonts.gstatic.com ' ,
68+ ),
69+ 'default-https-port ' => array (
70+ 'domain ' => 'https://static.example.com:443 ' ,
71+ 'expected ' => 'https://static.example.com ' ,
72+ ),
73+ 'custom-port ' => array (
74+ 'domain ' => 'https://cdn.example.com:8443 ' ,
75+ 'expected ' => 'https://cdn.example.com:8443 ' ,
76+ ),
77+ );
5278 }
5379
5480 /**
5581 * Unsafe or unclean domains are rejected.
5682 *
57- * @param string $domain Domain to validate.
83+ * @param string $domain Domain to validate.
84+ * @param string $expected_error Expected error message fragment.
5885 */
5986 #[DataProvider( 'rejectedDomainProvider ' )]
60- public function test_rejects_unsafe_or_unclean_domains ( string $ domain ): void {
87+ public function test_rejects_unsafe_or_unclean_domains ( string $ domain, string $ expected_error ): void {
6188 $ result = es_optimizer_validate_single_domain ( $ domain );
6289
6390 $ this ->assertFalse ( $ result ['valid ' ] );
64- $ this ->assertNotSame ( '' , $ result ['error ' ] );
91+ $ this ->assertStringContainsString ( $ domain , $ result ['error ' ] );
92+ $ this ->assertStringContainsString ( $ expected_error , $ result ['error ' ] );
6593 }
6694
6795 /**
6896 * Provide rejected domains.
6997 *
70- * @return array<string, array{domain: string}>
98+ * @return array<string, array{domain: string, expected_error: string }>
7199 */
72100 public static function rejectedDomainProvider (): array {
73101 return array (
74- 'http ' => array ( 'domain ' => 'http://example.com ' ),
75- 'path ' => array ( 'domain ' => 'https://example.com/file.css ' ),
76- 'query ' => array ( 'domain ' => 'https://example.com?cache=bust ' ),
77- 'credentials ' => array ( 'domain ' => 'https://user:pass@example.com ' ),
78- 'localhost ' => array ( 'domain ' => 'https://localhost ' ),
79- 'localhost-subdomain ' => array ( 'domain ' => 'https://cdn.localhost ' ),
80- 'special-use-domain ' => array ( 'domain ' => 'https://cache.internal ' ),
81- 'single-label-host ' => array ( 'domain ' => 'https://cdn ' ),
82- 'public-ip ' => array ( 'domain ' => 'https://8.8.8.8 ' ),
83- 'private-ip ' => array ( 'domain ' => 'https://192.168.1.1 ' ),
84- 'obfuscated-ip ' => array ( 'domain ' => 'https://127.000.000.001 ' ),
85- 'invalid-label ' => array ( 'domain ' => 'https://-example.com ' ),
86- 'invalid-port ' => array ( 'domain ' => 'https://example.com:0 ' ),
102+ 'http ' => array (
103+ 'domain ' => 'http://example.com ' ,
104+ 'expected_error ' => 'invalid URL ' ,
105+ ),
106+ 'path ' => array (
107+ 'domain ' => 'https://example.com/file.css ' ,
108+ 'expected_error ' => 'file paths are not allowed; use domains only ' ,
109+ ),
110+ 'query ' => array (
111+ 'domain ' => 'https://example.com?cache=bust ' ,
112+ 'expected_error ' => 'query parameters, fragments, and credentials are not allowed ' ,
113+ ),
114+ 'credentials ' => array (
115+ 'domain ' => 'https://user:pass@example.com ' ,
116+ 'expected_error ' => 'query parameters, fragments, and credentials are not allowed ' ,
117+ ),
118+ 'localhost ' => array (
119+ 'domain ' => 'https://localhost ' ,
120+ 'expected_error ' => 'IP addresses and private, local, or reserved hosts are not allowed ' ,
121+ ),
122+ 'localhost-subdomain ' => array (
123+ 'domain ' => 'https://cdn.localhost ' ,
124+ 'expected_error ' => 'IP addresses and private, local, or reserved hosts are not allowed ' ,
125+ ),
126+ 'special-use-domain ' => array (
127+ 'domain ' => 'https://cache.internal ' ,
128+ 'expected_error ' => 'IP addresses and private, local, or reserved hosts are not allowed ' ,
129+ ),
130+ 'single-label-host ' => array (
131+ 'domain ' => 'https://cdn ' ,
132+ 'expected_error ' => 'invalid hostname ' ,
133+ ),
134+ 'public-ip ' => array (
135+ 'domain ' => 'https://8.8.8.8 ' ,
136+ 'expected_error ' => 'IP addresses and private, local, or reserved hosts are not allowed ' ,
137+ ),
138+ 'private-ip ' => array (
139+ 'domain ' => 'https://192.168.1.1 ' ,
140+ 'expected_error ' => 'IP addresses and private, local, or reserved hosts are not allowed ' ,
141+ ),
142+ 'obfuscated-ip ' => array (
143+ 'domain ' => 'https://127.000.000.001 ' ,
144+ 'expected_error ' => 'IP addresses and private, local, or reserved hosts are not allowed ' ,
145+ ),
146+ 'invalid-label ' => array (
147+ 'domain ' => 'https://-example.com ' ,
148+ 'expected_error ' => 'invalid URL ' ,
149+ ),
150+ 'invalid-port ' => array (
151+ 'domain ' => 'https://example.com:0 ' ,
152+ 'expected_error ' => 'invalid port ' ,
153+ ),
87154 );
88155 }
89156
@@ -134,6 +201,34 @@ public function test_resource_hints_use_wordpress_filter_contract(): void {
134201 $ this ->assertContains ( 'https://static.example.com ' , $ dns_prefetch_urls );
135202 }
136203
204+ /**
205+ * Resource hints are not added when their feature flags are disabled.
206+ */
207+ public function test_resource_hints_are_not_added_when_feature_flags_are_disabled (): void {
208+ $ options = es_optimizer_get_default_options ();
209+ $ options ['enable_preconnect ' ] = 0 ;
210+ $ options ['preconnect_domains ' ] = 'https://cdn.example.com ' ;
211+ $ options ['enable_dns_prefetch ' ] = 0 ;
212+ $ options ['dns_prefetch_domains ' ] = 'https://static.example.com ' ;
213+
214+ update_option ( 'es_optimizer_options ' , $ options );
215+ es_optimizer_clear_options_cache ();
216+
217+ $ existing_preconnect_hints = array (
218+ array ( 'href ' => 'https://existing.example.com ' ),
219+ );
220+ $ existing_dns_prefetch_urls = array ( 'https://existing.example.com ' );
221+
222+ $ this ->assertSame (
223+ $ existing_preconnect_hints ,
224+ es_optimizer_add_preconnect_resource_hints ( $ existing_preconnect_hints , 'preconnect ' )
225+ );
226+ $ this ->assertSame (
227+ $ existing_dns_prefetch_urls ,
228+ es_optimizer_add_dns_prefetch_resource_hints ( $ existing_dns_prefetch_urls , 'dns-prefetch ' )
229+ );
230+ }
231+
137232 /**
138233 * Find a resource hint by href.
139234 *
0 commit comments